diff --git a/src/sPhone b/src/sPhone new file mode 100644 index 0000000..86b265a --- /dev/null +++ b/src/sPhone @@ -0,0 +1,1413 @@ +local function kernel(...) + _G.sPhone = { + version = "Beta 1", + user = "Guest", + devMode = false, + mainTerm = term.current(), + safeMode = false, + } + + if safemode then + sPhone.safeMode = true + safemode = nil + end + + sPhone.defaultApps = { + ["home"] = "sphone.home", + } + + sPhone.theme = { --Default colors + ["header"] = colors.blue, + ["headerText"] = colors.white, + ["text"] = colors.black, + ["background"] = "", + ["backgroundColor"] = colors.white, + ["window.background"] = colors.lightBlue, + ["window.side"] = colors.blue, + ["window.button"] = colors.lightBlue, + ["window.text"] = colors.white, + ["lock.background"] = colors.white, + ["lock.text"] = colors.black, + ["lock.inputBackground"] = colors.white, + ["lock.inputText"] = colors.black, + ["lock.inputSide"] = colors.lightBlue, + ["lock.error"] = colors.red, + } + + sPhone.defaultTheme = sPhone.theme + + if not fs.exists("/.sPhone/apis") then + fs.makeDir("/.sPhone/apis") + end + + for k, v in pairs(fs.list("/.sPhone/apis")) do + if not fs.isDir("/.sPhone/apis/"..v) then + os.loadAPI("/.sPhone/apis/"..v) + end + end + + if not config.write("/.sPhone/config/sPhone","newConfigFormat",true) then + os.pullEvent = os.pullEventRaw + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1,1) + printError("WARNING") + print("") + print("sPhone cannot load config file: corrupted") + print("To continue using sPhone all config data (.sPhone/config) will be erased") + printError("This action is irreversible!") + print("Y/N") + while true do + local _,k = os.pullEvent("char") + if string.lower(k) == "y" then + fs.delete("/.sPhone/config") + print("Config deleted") + print("Rebooting...") + sleep(3) + os.reboot() + elseif string.lower(k) == "n" then + print("Cannot delete config without user authorization") + print("Delete aborted") + print("Shutting down...") + error("Config corrupted",0) + end + end + end + + if not fs.exists("/.sPhone/system") then + fs.makeDir("/.sPhone/system") + end + + for k, v in pairs(fs.list("/.sPhone/system")) do + if not fs.isDir("/.sPhone/system/"..v) then + dofile("/.sPhone/system/"..v) + end + end + + local sPath = shell.path() + sPath = sPath..":/bin" + shell.setPath(sPath) + + if not fs.exists("/.sPhone/autorun") then + fs.makeDir("/.sPhone/autorun") + end + + term.setBackgroundColor(colors.white) + term.clear() + term.setCursorPos(1,1) + for k, v in pairs(fs.list("/.sPhone/autorun")) do + term.setTextColor(colors.black) + if not fs.isDir("/.sPhone/autorun/"..v) then + if not sPhone.safeMode then + local f = fs.open("/.sPhone/autorun/"..v,"r") + local script = f.readAll() + f.close() + print("Loading script "..v) + local ok, err = pcall(function() setfenv(loadstring(script),getfenv())() end) + if not ok then + term.setTextColor(colors.red) + print("Script error: "..v..": "..err) + fs.move("/.sPhone/autorun/"..v, "/.sPhone/autorun/disabled/"..v) + term.setTextColor(colors.blue) + print(v.." disabled to prevent errors") + end + else + print("Script "..v.." not loaded because Safe Mode") + end + end + end + + if config.read("/.sPhone/config/sPhone","username") then + sPhone.user = config.read("/.sPhone/config/sPhone","username") + end + + if not fs.exists("/.sPhone/config/sPhone") then + config.write("/.sPhone/config/sPhone","devMode",false) + end + + sPhone.devMode = config.read("/.sPhone/config/sPhone","devMode") + + if sPhone.devMode then + sPhone.crash = crash + end + + crash = nil + + function os.version() + return "sPhone "..sPhone.version + end + + function sPhone.getSize() + return term.getSize() + end + + local fileTheme = "/.sPhone/config/theme" + if fs.exists(fileTheme) then + sPhone.theme["header"] = (config.read(fileTheme, "header") or sPhone.theme["header"]) + sPhone.theme["headerText"] = (config.read(fileTheme, "headerText") or sPhone.theme["headerText"]) + sPhone.theme["text"] = (config.read(fileTheme, "text") or sPhone.theme["text"]) + sPhone.theme["background"] = (config.read(fileTheme, "background") or sPhone.theme["background"]) + sPhone.theme["backgroundColor"] = (config.read(fileTheme, "backgroundColor") or sPhone.theme["backgroundColor"]) + sPhone.theme["window.background"] = (config.read(fileTheme, "window.background") or sPhone.theme["window.background"]) + sPhone.theme["window.side"] = (config.read(fileTheme, "window.side") or sPhone.theme["window.side"]) + sPhone.theme["window.button"] = (config.read(fileTheme, "window.button") or sPhone.theme["window.button"]) + sPhone.theme["window.text"] = (config.read(fileTheme, "window.text") or sPhone.theme["window.text"]) + sPhone.theme["lock.background"] = (config.read(fileTheme, "lock.background") or sPhone.theme["lock.background"]) + sPhone.theme["lock.text"] = (config.read(fileTheme, "lock.text") or sPhone.theme["lock.text"]) + sPhone.theme["lock.inputBackground"] = (config.read(fileTheme, "lock.inputBackground") or sPhone.theme["lock.inputBackground"]) + sPhone.theme["lock.inputText"] = (config.read(fileTheme, "lock.inputText") or sPhone.theme["lock.inputText"]) + sPhone.theme["lock.inputSide"] = (config.read(fileTheme, "lock.inputSide") or sPhone.theme["lock.inputSide"]) + sPhone.theme["lock.error"] = (config.read(fileTheme, "lock.error") or sPhone.theme["lock.error"]) + else + for k, v in pairs(sPhone.theme) do + config.write(fileTheme, k, v) + end + end + + function sPhone.applyTheme(id, value) + if not value or not id then + error("bad arguement: double expected, got nil",2) + end + sPhone.theme[id] = value + config.write(fileTheme, id, value) + end + + function sPhone.getTheme(id) + if not id then + error("bad arguement: double expected, got nil",2) + end + local n = config.read(fileTheme, id) + if n then + return n + end + return sPhone.defaultTheme[id] + end + + function sPhone.setDefaultApp(app, path) + if not path then + error("got nil",2) + end + + sPhone.defaultApps[app] = path + config.write("/.sPhone/config/defaultApps",app,path) + end + + function sPhone.getDefaultApp(app) + if not app then + error("got nil",2) + end + + local n = config.read("/.sPhone/config/defaultApps",app) + return n + end + + if not fs.exists("/.sPhone/config/defaultApps") then + sPhone.setDefaultApp("home","/.sPhone/apps/home") + end + + function string.getExtension(name) + local ext = "" + local exten = false + name = string.reverse(name) + for i = 1, #name do + local s = string.sub(name,i,i) + if s == "." then + ch = i - 1 + exten = true + break + end + end + if exten then + ext = string.sub(name, 1, ch) + return string.reverse(ext) + else + return nil + end + end + + function sPhone.list(path, opt) + opt = opt or {} + opt.bg1 = opt.bg1 or sPhone.getTheme("backgroundColor") + opt.fg1 = opt.fg1 or sPhone.getTheme("text") + opt.fg1b = opt.fg1b or colors.lime + opt.bg2 = opt.bg2 or sPhone.getTheme("header") + opt.fg2 = opt.fg2 or sPhone.getTheme("headerText") + opt.bg3 = opt.bg3 or sPhone.getTheme("header") + opt.fg3 = opt.fg3 or sPhone.getTheme("headerText") + opt.output = opt.output or true + opt.list = opt.list or false + opt.pairs = opt.pairs or false + opt.title = opt.title or false + if not path then + path = "" + end + if not fs.isDir(path) and not opt.list then + error("Invalid path") + end + local scroll = 0 + local items + local cho = {} + local w, h + local function rebuild() + local files, dirs = {}, {} + items = {} + local flist + if not opt.list then + flist = fs.list(path) + else + flist = opt.list + end + + local function pair(tab) + if opt.pairs then + return pairs(tab) + end + return ipairs(tab) + end + + for i, v in pair(flist) do + if fs.isDir(fs.combine(path, v)) then + table.insert(dirs, v) + else + table.insert(files, v) + end + end + table.sort(files) + table.sort(dirs) + for i, v in pair(dirs) do + table.insert(items, v) + end + for i, v in pair(files) do + table.insert(items, v) + end + + if opt.pairs then + for k, v in pairs(flist) do + cho[v] = k + end + end + scroll = 0 + end + rebuild() + local setVisible = term.current().setVisible + or function()end + local function redraw() + w, h = term.getSize() + setVisible(false) + term.setBackgroundColor(opt.bg1) + term.clear() + for i = scroll + 1, h + scroll - 1 do + local str = items[i] + if str then + term.setCursorPos(2, 1 + i - scroll) + local isDir + if not pairs then + isDir = fs.isDir(fs.combine(path, str)) + else + isDir = false + end + term.setTextColor(isDir and opt.fg1b or opt.fg1) + local _w = w - (isDir and 2 or 1) + if #str > _w then + str = str:sub(1, _w - 2) .. ".." + end + if isDir then + str = str .. "/" + end + term.write(str) + end + end + term.setBackgroundColor(opt.bg2) + term.setTextColor(opt.fg2) + term.setCursorPos(1, 1) + term.clearLine() + local _path = path .. "/" + if #_path > w - 2 then + _path = ".." .. _path:sub(-w + 4) + end + if opt.title then + _path = opt.title + end + term.write(_path) + term.setBackgroundColor(opt.bg3) + term.setTextColor(opt.fg3) + term.setCursorPos(w, 1) + term.write("X") + term.setCursorPos(w, 2) + term.write("^") + term.setCursorPos(w, h) + term.write("v") + setVisible(true) + end + while true do + redraw() + local ev = {os.pullEventRaw()} + if ev[1] == "terminate" then + return nil + elseif ev[1] == "mouse_scroll" and ev[4] > 1 then + scroll = scroll + ev[2] + elseif ev[1] == "mouse_click" then + if ev[3] == w and ev[2] == 1 then + if ev[4] == 1 then + return nil + elseif ev[4] == 2 then + scroll = scroll - 1 + elseif ev[4] == h then + scroll = scroll + 1 + end + elseif ev[3] < w and ev[4] == 1 and ev[2] == 1 then + path = fs.getDir(path) + if path == ".." then + path = "" + end + rebuild() + elseif ev[3] < w and ev[4] > 1 then + local item = items[ev[4] + scroll - 1] + if item then + local fullPath = fs.combine(path, item) + if fs.isDir(fullPath) then + path = fullPath + rebuild() + else + if opt.output then + if opt.pairs then + return cho[fullPath], fullPath, ev[2] + end + return fullPath, ev[2] + end + end + end + end + end + scroll = math.min(math.max(0, scroll), math.max(0, #items - h + 1)) + end + end + + function sPhone.read( _sReplaceChar, _tHistory, _fnComplete, _MouseEvent, _presetInput ) + term.setCursorBlink( true ) + + local sLine = _presetInput + + if type(sLine) ~= "string" then + sLine = "" + end + local nPos = #sLine + local nHistoryPos + local _MouseX + local _MouseY + local param + local sEvent + local usedMouse = false + if _sReplaceChar then + _sReplaceChar = string.sub( _sReplaceChar, 1, 1 ) + end + + local tCompletions + local nCompletion + local function recomplete() + if _fnComplete and nPos == string.len(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.getCursorPos() + + local function redraw( _bClear ) + local nScroll = 0 + if sx + nPos >= w then + nScroll = (sx + nPos) - w + end + + local cx,cy = term.getCursorPos() + term.setCursorPos( sx, cy ) + local sReplace = (_bClear and " ") or _sReplaceChar + if sReplace then + term.write( string.rep( sReplace, math.max( string.len(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, string.len( sCompletion ) ) ) + else + term.write( sCompletion ) + end + if not _bClear then + term.setTextColor( oldText ) + term.setBackgroundColor( oldBg ) + end + end + + term.setCursorPos( 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 = string.len( sLine ) + + -- Redraw + recomplete() + redraw() + end + end + while true do + sEvent, param,_MouseX,_MouseY = os.pullEvent() + 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 + string.len( param ) + recomplete() + redraw() + + elseif sEvent == "key" then + if param == keys.enter then + -- 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 < string.len(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 = string.len( sLine ) + else + sLine = "" + nPos = 0 + end + uncomplete() + redraw() + + end + + elseif param == keys.backspace then + -- Backspace + if nPos > 0 then + clear() + sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 ) + nPos = nPos - 1 + 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 < string.len(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 < string.len(sLine ) then + clear() + nPos = string.len(sLine) + recomplete() + redraw() + end + + elseif param == keys.tab then + -- Tab (accept autocomplete) + acceptCompletion() + + end + + elseif sEvent == "term_resize" then + -- Terminal resized + w = term.getSize() + redraw() + + elseif sEvent == "mouse_click" and _MouseEvent then + if nCompletion then + clear() + uncomplete() + redraw() + end + usedMouse = true + break + end + end + + local cx, cy = term.getCursorPos() + term.setCursorBlink( false ) + term.setCursorPos( w + 1, cy ) + print() + if sEvent == "mouse_click" then + return sLine, param, _MouseX, _MouseY + end + return sLine + end + + local function clear() + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.clear() + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["text"]) + end + + sPhone.forceShutdown = os.shutdown + sPhone.forceReboot = os.reboot + + function os.shutdown() + local w, h = term.getSize() + local text = "Shutting down" + local x = math.ceil(w/2)-math.ceil(#text/2)+1 + local y = math.ceil(h/2) + sPhone.inHome = false + os.pullEvent = os.pullEventRaw + local function printMsg(color) + term.setBackgroundColor(color) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(x,y) + print(text) + sleep(0.1) + end + printMsg(colors.white) + printMsg(colors.lightGray) + printMsg(colors.gray) + printMsg(colors.black) + sleep(0.6) + sPhone.forceShutdown() + end + + function os.reboot() + local w, h = term.getSize() + local text = "Rebooting" + local x = math.ceil(w/2)-math.ceil(#text/2)+1 + local y = math.ceil(h/2) + sPhone.inHome = false + os.pullEvent = os.pullEventRaw + local function printMsg(color) + term.setBackgroundColor(color) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(x,y) + print(text) + sleep(0.1) + end + printMsg(colors.white) + printMsg(colors.lightGray) + printMsg(colors.gray) + printMsg(colors.black) + sleep(0.6) + sPhone.forceReboot() + end + + function sPhone.header(title, butt) + + if not title then + title = "sPhone" + end + + local w, h = term.getSize() + paintutils.drawLine(1,1,w,1, sPhone.theme["header"]) + term.setTextColor(sPhone.theme["headerText"]) + term.setCursorPos(1,1) + write(" "..title) + term.setCursorPos(w,1) + if butt then + write(butt) + end + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + term.setCursorPos(1,3) + end + + function sPhone.menu(items, title, closeButton) + local function cprint(text) + if type(text) ~= 'table' then + text = {text} + end + + local w, h = term.getSize() + + for i=1,#text do + local x, y = term.getCursorPos() + term.setCursorPos(math.floor(w/2)-math.floor(text[i]:len()/2), y) + print(text[i]) + end + end + local function clear() + term.clear() + term.setCursorPos(1, 1) + end + + local termWidth, termHeight = term.getSize() + local drawSize = termHeight - 2 + + local function maxPages() + local itemCount = #items + local pageCount = 0 + while itemCount > 0 do + itemCount = itemCount - drawSize + pageCount = pageCount + 1 + end + return pageCount + end + + local function iif(cond, trueval, falseval) + if cond then + return trueval + else + return falseval + end + end + + local function pagedItems() + local ret = {} + for i = 1, maxPages() do + local tmp = {} + local nElements = 0 + for j = drawSize*(i-1)+1, iif(drawSize*(i+1) > #items, #items, drawSize*(i+1)) do + if nElements < drawSize then + table.insert(tmp, items[j]) + nElements = nElements + 1 + end + end + table.insert(ret, tmp) + end + return ret + end + + local selected = 1 + if start then + selected = start + end + local page = 1 + + local function redraw() + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + term.clear() + term.setCursorPos(1,1) + if not title then + title = "" + end + sPhone.header(title,closeButton) + term.setCursorPos(1,3) + if moreTitle then + head = moreTitle + else + head = {} + if not allowNil or allowNil == true then + --head[3] = 'Terminate to cancel.' + end + end + for i=1,#head do + print(head[i]) + end + if maxPages() > 1 then + pages = "<- (page "..page.." of "..maxPages()..") ->" + print(pages) + end + for i = 1, #pagedItems()[page] do + if selected == drawSize*(page-1)+i then + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + else + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + end + term.clearLine() + cprint(iif(selected == drawSize*(page-1)+i,"","").." "..pagedItems()[page][i]) + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + end + end + + local function changePage(pW) + if pW == 1 and page < maxPages() then + page = page + 1 + if selected + drawSize > #items then + selected = #items + else + selected = selected + drawSize + end + elseif pW == -1 and page > 1 then + page = page - 1 + if selected - drawSize < 1 then + selected = 1 + else + selected = selected - drawSize + end + end + end + + while true do + redraw() + local eventData = {os.pullEventRaw()} + if eventData[1] == 'mouse_click' then + if eventData[4] == 1 and eventData[3] == termWidth then + return false, 0 + elseif eventData[4] > 2 then + clear() + selected = (eventData[4]-3+((page-1)*drawSize))+1 + if selected then + return items[selected], selected + end + end + end + end +end + + function sPhone.yesNo(title, desc, hideUser) + term.setCursorBlink(false) + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.clear() + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["text"]) + local w, h = term.getSize() + paintutils.drawLine(1,1,w,1, sPhone.theme["header"]) + term.setTextColor(sPhone.theme["headerText"]) + term.setCursorPos(1,1) + if not hideUser then + if not sPhone.user then + write(" sPhone") + else + write(" "..sPhone.user) + end + end + term.setCursorPos(1,3) + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.setTextColor(sPhone.theme["text"]) + visum.align("center", " "..title, false, 3) + if desc then + term.setCursorPos(2,6) + print(desc) + end + paintutils.drawFilledBox(3, 16, 9, 18, colors.green) + paintutils.drawFilledBox(18, 16, 24, 18, colors.red) + term.setTextColor(colors.white) + term.setCursorPos(5,17) + term.setBackgroundColor(colors.green) + write("Yes") + term.setCursorPos(20,17) + term.setBackgroundColor(colors.red) + write("No") + while true do + local _,_,x,y = os.pullEvent("mouse_click") + if (x > 2 and y > 15) and (x < 10 and y < 19) then + return true + elseif (x > 17 and y > 15) and (x < 25 and y < 19) then + return false + end + end + end + + function sPhone.winOk(fmessage, smessage, bg, side, text, button) + if not fmessage then + fmessage = "" + end + if not smessage then + smessage = "" + end + if not bg then + bg = sPhone.theme["window.background"] + end + if not text then + text = sPhone.theme["window.text"] + end + if not button then + button = sPhone.theme["window.button"] + end + if not side then + side = sPhone.theme["window.side"] + end + term.setCursorBlink(false) + if #fmessage >= #smessage then + local w, h = term.getSize + term.setBackgroundColor(side) + paintutils.drawBox(12 - math.ceil(#fmessage / 2), 5, 15 + math.ceil(#fmessage / 2), 10, side) + term.setBackgroundColor(bg) + paintutils.drawFilledBox(13 - math.ceil(#fmessage / 2), 6, 14 + math.ceil(#fmessage / 2), 9, bg) + term.setCursorPos(14 - math.ceil(#fmessage / 2), 7) + term.setTextColor(text) + write(fmessage) + term.setCursorPos(14 - math.ceil(#smessage / 2), 8) + write(smessage) + else + local w, h = term.getSize + term.setBackgroundColor(side) + paintutils.drawBox(12 - math.ceil(#smessage / 2), 5, 15 + math.ceil(#smessage / 2), 10, side) + term.setBackgroundColor(bg) + paintutils.drawFilledBox(13 - math.ceil(#smessage / 2), 6, 14 + math.ceil(#smessage / 2), 9, bg) + term.setCursorPos(14 - math.ceil(#fmessage / 2), 7) + term.setTextColor(text) + write(fmessage) + term.setCursorPos(14 - math.ceil(#smessage / 2), 8) + write(smessage) + end + term.setCursorPos(13,10) + term.setBackgroundColor(button) + write("Ok") + while true do + local e, k, x,y = os.pullEvent() + if e == "mouse_click" then + if y == 10 then + if x == 13 or x == 14 then + return + end + end + elseif e == "key" then + if k == 28 then + return + end + end + end + end + + function sPhone.colorPicker(message, old) -- From Impulse + local current = math.log(old) / math.log(2) + -- first line is already code wizardry + local function redraw() + term.setBackgroundColour(sPhone.theme["backgroundColor"]) + term.clear() + sPhone.header(message) + term.setCursorPos(2,5) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.lime) + write(" Ok ") + term.setCursorPos(7,5) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.red) + write(" Cancel ") + term.setTextColor(colors.black) + term.setCursorPos(2, 3) + for i = 0, 15 do + term.setBackgroundColour(2^i) + term.write(i == current and "#" or ":") + end + end + while true do + redraw() + local ev = {os.pullEvent()} + if ev[1] == "key" and ev[2] == keys.enter then + return 2^current + elseif ev[1] == "mouse_click" then + if ev[4] == 3 and ev[3] >= 2 and ev[3] <= 17 then + current = ev[3] - 2 % 16 + elseif ev[4] == 5 and ev[3] >= 2 and ev[3] <= 6 then + return 2^current + elseif ev[4] == 5 and ev[3] >= 7 and ev[3] <= 14 then + return old + end + end + end + end + + sPhone.colourPicker = sPhone.colorPicker -- For UK + + function sPhone.install(spk) + if not fs.exists("/.sPhone/config/spklist") then + local f = fs.open("/.sPhone/config/spklist","w") + f.write("{}") + f.close() + end + if string.getExtension(spk) == "spk" then + if fs.exists(spk) and not fs.isDir(spk) then + local f = fs.open(spk,"r") + local script = f.readAll() + f.close() + script = textutils.unserialize(script) + if not script then + error("spk corrupted",2) + end + + local function writeFile(patha,contenta) + local file = fs.open(patha,"w") + file.write(contenta) + file.close() + end + function writeDown(inputa,dira) + for i,v in pairs(inputa) do + if type(v) == "table" then + writeDown(v,dira.."/"..i) + elseif type(v) == "string" then + writeFile(dira.."/"..i,v) + end + end + end + + local _config = textutils.unserialize(script.config) + if not _config.id then + error("SPK: id not found",2) + end + if not _config.main then + error("SPK: main not found",2) + end + writeDown(textutils.unserialize(script.files),"/.sPhone/apps/spk/".._config.id.."/files") + local f = fs.open("/.sPhone/apps/spk/".._config.id.."/.spk","w") + f.write(textutils.serialize(_config)) + f.close() + local f = fs.open("/.sPhone/config/spklist","r") + local lists = f.readAll() + f.close() + lists = textutils.unserialize(lists) + if not lists then + error("Cannot open config",2) + end + + if not _config.name then + _config.name = _config.id + end + + lists[_config.id] = _config.name + + local f = fs.open("/.sPhone/config/spklist","w") + f.write(textutils.serialize(lists)) + f.close() + return true, _config.id + else + return false, "not a spk file" + end + else + return false, "not a spk file" + end + end + + function sPhone.launch(spk) + if not fs.exists("/.sPhone/config/spklist") then + local f = fs.open("/.sPhone/config/spklist","w") + f.write("{}") + f.close() + end + local f = fs.open("/.sPhone/config/spklist","r") + local lists = f.readAll() + f.close() + lists = textutils.unserialize(lists) + if not lists then + error("Cannot open config",2) + end + + if not lists[spk] then + return false, "not installed" + end + + if not fs.exists("/.sPhone/apps/spk/"..spk.."/.spk") then + error("Invalid SPK, .spk not found",2) + end + + local f = fs.open("/.sPhone/apps/spk/"..spk.."/.spk","r") + local script = f.readAll() + f.close() + _config = textutils.unserialize(script) + if not script then + error("config corrupted",2) + end + local ok, err = pcall(function() + setfenv(loadfile(fs.combine("/.sPhone/apps/spk",_config.id.."/files/".._config.main)), setmetatable({ + spk = { + getName = function() + return (_config.name or nil) + end, + + getID = function() + return (_config.id or nil) + end, + + getPath = function() + return "/.sPhone/apps/spk/".._config.id + end, + + getDataPath = function() + return "/.sPhone/apps/spk/".._config.id.."/data" + end, + + getAuthor = function() + return (_config.author or nil) + end, + + getVersion = function() + return (_config.version or nil) + end, + + getType = function() + return (_config.type or nil) + end, + + open = function(file, mode) + return fs.open("/.sPhone/apps/spk/".._config.id.."/data/"..file,mode) + end, + }, + string = string, + sPhone = sPhone, + }, {__index = getfenv()}))() + end) + + if not ok then + return false, err + end + return true + end + + local function home() + sPhone.inHome = true + while true do + local homeSPKs = { + "sphone.home", + } + + if not config.read("/.sPhone/config/spklist","sphone.home") then + sPhone.install("/.sPhone/apps/home.spk") + end + os.pullEvent = os.oldPullEvent + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1,1) + local homeID = sPhone.getDefaultApp("home") + if not sPhone.safeMode then + if not config.list("/.sPhone/config/spklist")[homeID] then + homeID = "sphone.home" + end + else + homeID = "sphone.home" + end + + temp.set("homePID",task.add(function() sPhone.launch(homeID) end)) + task.run() + + end + sPhone.inHome = false + end + + function login() + if config.read("/.sPhone/config/sPhone","lockEnabled") == nil then + config.write("/.sPhone/config/sPhone","lockEnabled",true) + end + local usingPW = config.read("/.sPhone/config/sPhone","lockEnabled") + if not usingPW then + local old = os.pullEvent + os.pullEvent = os.pullEventRaw + local fEvents = { + ["mouse_drag"] = true, + ["mouse_click"] = true, + ["key"] = false, + } + while true do + local w,h = term.getSize() + local clockS = textutils.formatTime(os.time(), not config.read("/.sPhone/config/sPhone","format12time")) + term.setBackgroundColor(sPhone.theme["lock.background"]) + term.clear() + term.setCursorPos(1,1) + sPhone.header(sPhone.user) + term.setBackgroundColor(sPhone.theme["lock.background"]) + term.setTextColor(sPhone.theme["lock.text"]) + term.setCursorPos(6,4) + bigfont.bigPrint(clockS) + visum.align("center"," Slide to unlock",false,h) + local clockUpdate = os.startTimer(1) + local e = {os.pullEvent()} + if fEvents[e[1]] then + os.pullEvent = old + break + end + end + return + end + local old = os.pullEvent + os.pullEvent = os.pullEventRaw + sPhone.locked = true + if config.read("/.sPhone/config/sPhone","password") then + while true do + term.setBackgroundColor(sPhone.theme["lock.background"]) + term.clear() + term.setCursorPos(1,1) + sPhone.header(sPhone.user) + paintutils.drawBox(7,9,20,11,sPhone.theme["lock.inputSide"]) + if sPhone.wrongPassword then + term.setTextColor(sPhone.theme["lock.error"]) + term.setBackgroundColor(sPhone.theme["lock.background"]) + visum.align("center"," Wrong Password",false,13) + end + term.setTextColor(sPhone.theme["lock.text"]) + term.setBackgroundColor(sPhone.theme["lock.background"]) + visum.align("center"," Insert Password",false,7) + local loginTerm = window.create(term.native(), 8,10,12,1, true) + term.redirect(loginTerm) + term.setBackgroundColor(sPhone.theme["lock.inputBackground"]) + term.clear() + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["lock.inputText"]) + local passwordLogin = read("*") + term.redirect(sPhone.mainTerm) + local fpw = config.read("/.sPhone/config/sPhone","password") + if sha256.sha256(passwordLogin) == fpw then + sPhone.wrongPassword = false + os.pullEvent = old + return + else + sPhone.wrongPassword = true + end + end + else + local name + local pw + local pwr + local rServer + local password1,mouse,x,y + local w, h = term.getSize() + local skipped = false + sPhone.firstBoot = true + + for k,v in ipairs(fs.list("/.sPhone/apps/system")) do + sPhone.install("/.sPhone/apps/system/"..v) + end + + while not skipped do + + term.setBackgroundColor(sPhone.theme["lock.background"]) + term.clear() + term.setCursorPos(1,1) + sPhone.header("Setup") + paintutils.drawBox(7,9,20,11,sPhone.theme["lock.inputSide"]) + if sPhone.wrongPassword then + term.setTextColor(sPhone.theme["lock.error"]) + term.setBackgroundColor(sPhone.theme["lock.background"]) + visum.align("center"," Wrong Password",false,13) + end + term.setTextColor(sPhone.theme["lock.text"]) + term.setBackgroundColor(sPhone.theme["lock.background"]) + visum.align("center"," Insert Password",false,7) + local t = "Skip" + term.setCursorPos(w-#t+1,h) + write(t) + local loginTerm = window.create(term.native(), 8,10,12,1, true) + term.redirect(loginTerm) + term.setBackgroundColor(sPhone.theme["lock.inputBackground"]) + term.clear() + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["lock.inputText"]) + while true do + password1,mouse,x,y = sPhone.read("*",nil,nil,true) + if mouse then + if y == h and (x >= 23 and x <= w) then + skipped = true + config.write("/.sPhone/config/sPhone","lockEnabled",false) + break + end + else + break + end + end + term.redirect(sPhone.mainTerm) + if not skipped then + term.setBackgroundColor(sPhone.theme["lock.background"]) + term.clear() + term.setCursorPos(1,1) + sPhone.header("Setup") + paintutils.drawBox(7,9,20,11,sPhone.theme["lock.inputSide"]) + term.setTextColor(sPhone.theme["lock.text"]) + term.setBackgroundColor(sPhone.theme["lock.background"]) + visum.align("center"," Repeat",false,7) + local loginTerm = window.create(term.native(), 8,10,12,1, true) + term.redirect(loginTerm) + term.setBackgroundColor(sPhone.theme["lock.inputBackground"]) + term.clear() + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["lock.inputText"]) + local password2 = read("*") + term.redirect(sPhone.mainTerm) + if password1 == password2 then + config.write("/.sPhone/config/sPhone", "password",sha256.sha256(password1)) + term.setTextColor(colors.lime) + visum.align("center"," Password set!",false,13) + sleep(2) + break + else + sPhone.wrongPassword = true + end + end + end + + local name + + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.clear() + sPhone.header("Setup") + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["text"]) + term.setCursorPos(2,3) + visum.align("center","Username",false,3) + term.setCursorPos(2,5) + local newUsername = read() + config.write("/.sPhone/config/sPhone","username",newUsername) + + if fs.exists("/.sPhone/config/sPhone") then + name = config.read("/.sPhone/config/sPhone","username") + else + name = "Guest" + end + term.setBackgroundColor(sPhone.theme["backgroundColor"]) + term.clear() + sPhone.header("Setup") + term.setCursorPos(1,1) + term.setTextColor(sPhone.theme["text"]) + sPhone.user = name + os.setComputerLabel(sPhone.user.."'s sPhone") + visum.align("center"," All Set!",false,3) + visum.align("center"," Have fun with sPhone",false,5) + sleep(2) + sPhone.locked = false + sPhone.inHome = true + sPhone.firstBoot = false + os.pullEvent = old + return + end + end + + sPhone.lock = login + sPhone.login = login + + http.request("https://raw.githubusercontent.com/BeaconNet/sPhone/master/src/version") + local newVersion + local timeout = os.startTimer(2) + while true do + local event,url, sourceText = os.pullEvent() + if event == "http_success" then + newVersion = sourceText.readLine() + sourceText.close() + break + elseif event == "http_failure" then + newVersion = sPhone.version + break + end + end + + if newVersion ~= sPhone.version then + sPhone.newUpdate = true + else + sPhone.newUpdate = false + end + + login() + if sPhone.newUpdate then + sPhone.winOk("New Update!") + end + home() + +end +if not sPhone then + kernel({...}) +else + print("sPhone already started") +end