SHOW:
|
|
- or go back to the newest paste.
| 1 | -- LemonOS: Pocket Shell for Lemonphone | |
| 2 | -- by ChatGPT (GPT-5) | |
| 3 | -- Features: Persistent settings, live clock, signal bars, auto-netchat install, firmware info | |
| 4 | ||
| 5 | local CONFIG_PATH = "/lemonos.cfg" | |
| 6 | local NETCHAT_PATH = "/netchat" | |
| 7 | local STARTUP_PATH = "/startup.lua" | |
| 8 | ||
| 9 | -- Firmware info | |
| 10 | local firmware = {
| |
| 11 | device = "Lemonphone Pocket Computer", | |
| 12 | version = "LemonOS v1.0.0", | |
| 13 | } | |
| 14 | ||
| 15 | local config = {
| |
| 16 | username = "User", | |
| 17 | accentColor = colors.yellow, | |
| 18 | bgColor = colors.black, | |
| 19 | rednetSide = "back", | |
| 20 | } | |
| 21 | ||
| 22 | -- === CONFIG LOAD/SAVE === | |
| 23 | local function saveConfig() | |
| 24 | local file = fs.open(CONFIG_PATH, "w") | |
| 25 | if file then | |
| 26 | file.write(textutils.serialize(config)) | |
| 27 | file.close() | |
| 28 | end | |
| 29 | end | |
| 30 | ||
| 31 | local function loadConfig() | |
| 32 | if fs.exists(CONFIG_PATH) then | |
| 33 | local file = fs.open(CONFIG_PATH, "r") | |
| 34 | if file then | |
| 35 | local data = file.readAll() | |
| 36 | file.close() | |
| 37 | local ok, tbl = pcall(textutils.unserialize, data) | |
| 38 | if ok and type(tbl) == "table" then | |
| 39 | for k, v in pairs(tbl) do | |
| 40 | config[k] = v | |
| 41 | end | |
| 42 | end | |
| 43 | end | |
| 44 | end | |
| 45 | end | |
| 46 | loadConfig() | |
| 47 | ||
| 48 | -- === AUTO DOWNLOAD NETCHAT === | |
| 49 | if not fs.exists(NETCHAT_PATH) then | |
| 50 | print("Downloading netchat...")
| |
| 51 | if http then | |
| 52 | local success, response = pcall(http.get, "https://pastebin.com/raw/GwmGxjwy") | |
| 53 | if success and response then | |
| 54 | local content = response.readAll() | |
| 55 | response.close() | |
| 56 | local file = fs.open(NETCHAT_PATH, "w") | |
| 57 | file.write(content) | |
| 58 | file.close() | |
| 59 | print("netchat installed!")
| |
| 60 | else | |
| 61 | print("Failed to download netchat.")
| |
| 62 | end | |
| 63 | else | |
| 64 | print("HTTP API not enabled, cannot download netchat.")
| |
| 65 | end | |
| 66 | -- Set startup to shell.run("firm.lpsw")
| |
| 67 | local file = fs.open(STARTUP_PATH, "w") | |
| 68 | file.write('shell.run("firm.lpsw")')
| |
| 69 | file.close() | |
| 70 | end | |
| 71 | ||
| 72 | -- === INITIALIZE REDNET === | |
| 73 | if peripheral.find("modem") then
| |
| 74 | rednet.open(config.rednetSide) | |
| 75 | else | |
| 76 | rednet.close(config.rednetSide) | |
| 77 | end | |
| 78 | ||
| 79 | -- === UTILITIES === | |
| 80 | local function centerText(y, text, color) | |
| 81 | local w = term.getSize() | |
| 82 | local x = math.floor((w - #text) / 2) | |
| 83 | term.setCursorPos(x, y) | |
| 84 | if color then term.setTextColor(color) end | |
| 85 | term.write(text) | |
| 86 | term.setTextColor(colors.white) | |
| 87 | end | |
| 88 | ||
| 89 | local function getSignalStrength() | |
| 90 | if not rednet.isOpen(config.rednetSide) then return 0 end | |
| 91 | local peers = { rednet.lookup("lemonphone_ping") }
| |
| 92 | if #peers == 0 then | |
| 93 | return 1 | |
| 94 | elseif #peers < 3 then | |
| 95 | return 2 | |
| 96 | else | |
| 97 | return 3 | |
| 98 | end | |
| 99 | end | |
| 100 | ||
| 101 | local function drawSignalBars(strength) | |
| 102 | local barChar = "|" | |
| 103 | local maxBars = 3 | |
| 104 | for i = 1, maxBars do | |
| 105 | if i <= strength then | |
| 106 | term.setTextColor(colors.lime) | |
| 107 | else | |
| 108 | term.setTextColor(colors.gray) | |
| 109 | end | |
| 110 | term.write(barChar) | |
| 111 | end | |
| 112 | term.setTextColor(colors.white) | |
| 113 | end | |
| 114 | ||
| 115 | local function drawTopBar() | |
| 116 | local w = term.getSize() | |
| 117 | term.setCursorPos(1, 1) | |
| 118 | term.setBackgroundColor(config.accentColor) | |
| 119 | term.clearLine() | |
| 120 | ||
| 121 | -- Left: signal strength | |
| 122 | term.setCursorPos(2, 1) | |
| 123 | if rednet.isOpen(config.rednetSide) then | |
| 124 | drawSignalBars(getSignalStrength()) | |
| 125 | term.write(" Net")
| |
| 126 | else | |
| 127 | term.setTextColor(colors.red) | |
| 128 | term.write("X NoNet")
| |
| 129 | term.setTextColor(colors.white) | |
| 130 | end | |
| 131 | ||
| 132 | -- Right: time and date | |
| 133 | local timeStr = textutils.formatTime(os.time(), true) | |
| 134 | local dateStr = os.date("%b %d")
| |
| 135 | local clock = timeStr .. " | " .. dateStr | |
| 136 | term.setCursorPos(w - #clock - 1, 1) | |
| 137 | term.setTextColor(colors.white) | |
| 138 | term.write(clock) | |
| 139 | end | |
| 140 | ||
| 141 | local function drawMenu(selected) | |
| 142 | local apps = {
| |
| 143 | {name = "Messages", cmd = "netchat join"},
| |
| 144 | {name = "Files", cmd = "shell"},
| |
| 145 | {name = "Settings", cmd = "settings"},
| |
| 146 | {name = "About Lemonphone", cmd = "about"},
| |
| 147 | {name = "Reboot", cmd = "reboot"}
| |
| 148 | } | |
| 149 | ||
| 150 | local _, h = term.getSize() | |
| 151 | local startY = 4 | |
| 152 | term.setTextColor(colors.white) | |
| 153 | for i, app in ipairs(apps) do | |
| 154 | term.setCursorPos(4, startY + i - 1) | |
| 155 | if i == selected then | |
| 156 | term.setBackgroundColor(colors.gray) | |
| 157 | term.setTextColor(colors.black) | |
| 158 | else | |
| 159 | term.setBackgroundColor(config.bgColor) | |
| 160 | term.setTextColor(colors.white) | |
| 161 | end | |
| 162 | term.clearLine() | |
| 163 | term.setCursorPos(4, startY + i - 1) | |
| 164 | term.write(app.name) | |
| 165 | end | |
| 166 | ||
| 167 | term.setBackgroundColor(config.bgColor) | |
| 168 | return apps | |
| 169 | end | |
| 170 | ||
| 171 | -- === SETTINGS MENU === | |
| 172 | local function openSettings() | |
| 173 | while true do | |
| 174 | term.setBackgroundColor(config.bgColor) | |
| 175 | term.clear() | |
| 176 | centerText(3, "Settings", config.accentColor) | |
| 177 | term.setCursorPos(2, 5) | |
| 178 | term.write("1. Change Username")
| |
| 179 | term.setCursorPos(2, 6) | |
| 180 | term.write("2. Change Accent Color")
| |
| 181 | term.setCursorPos(2, 7) | |
| 182 | term.write("3. Back")
| |
| 183 | ||
| 184 | local _, key = os.pullEvent("key")
| |
| 185 | if key == keys.one then | |
| 186 | term.setCursorPos(2, 9) | |
| 187 | term.clearLine() | |
| 188 | term.write("Enter new username: ")
| |
| 189 | config.username = read() | |
| 190 | saveConfig() | |
| 191 | elseif key == keys.two then | |
| 192 | term.setCursorPos(2, 9) | |
| 193 | term.clearLine() | |
| 194 | term.write("Color name (e.g. yellow): ")
| |
| 195 | local colorName = read() | |
| 196 | if colors[colorName] then | |
| 197 | config.accentColor = colors[colorName] | |
| 198 | saveConfig() | |
| 199 | else | |
| 200 | term.setCursorPos(2, 10) | |
| 201 | term.write("Invalid color.")
| |
| 202 | sleep(1) | |
| 203 | end | |
| 204 | elseif key == keys.three then | |
| 205 | return | |
| 206 | end | |
| 207 | end | |
| 208 | end | |
| 209 | ||
| 210 | -- === ABOUT LEMONPHONE === | |
| 211 | local function aboutLemonphone() | |
| 212 | term.setBackgroundColor(config.bgColor) | |
| 213 | term.clear() | |
| 214 | centerText(3, "About Lemonphone", config.accentColor) | |
| 215 | term.setCursorPos(2, 5) | |
| 216 | term.write("Device: " .. firmware.device)
| |
| 217 | term.setCursorPos(2, 6) | |
| 218 | term.write("Firmware: " .. firmware.version)
| |
| 219 | term.setCursorPos(2, 8) | |
| 220 | term.write("Press any key to return...")
| |
| 221 | os.pullEvent("key")
| |
| 222 | end | |
| 223 | ||
| 224 | -- === MAIN LOOP === | |
| 225 | - | local selected = 1 |
| 225 | + | -- Pre-draw everything once |
| 226 | term.setBackgroundColor(config.bgColor) | |
| 227 | term.clear() | |
| 228 | drawTopBar() | |
| 229 | centerText(3, config.username .. "'s Lemonphone", config.accentColor) | |
| 230 | local apps = drawMenu(selected) | |
| 231 | ||
| 232 | - | drawTopBar() |
| 232 | + | |
| 233 | - | centerText(3, config.username .. "'s Lemonphone", config.accentColor) |
| 233 | + | |
| 234 | - | local apps = drawMenu(selected) |
| 234 | + | |
| 235 | while true do | |
| 236 | local event, key = os.pullEvent() | |
| 237 | ||
| 238 | if event == "key" then | |
| 239 | local oldSelected = selected | |
| 240 | if key == keys.up then | |
| 241 | selected = math.max(1, selected - 1) | |
| 242 | elseif key == keys.down then | |
| 243 | selected = math.min(#apps, selected + 1) | |
| 244 | elseif key == keys.enter then | |
| 245 | local chosen = apps[selected] | |
| 246 | if chosen.name == "Settings" then | |
| 247 | openSettings() | |
| 248 | elseif chosen.name == "About Lemonphone" then | |
| 249 | - | term.setBackgroundColor(config.bgColor) |
| 249 | + | |
| 250 | - | term.clear() |
| 250 | + | |
| 251 | - | term.setCursorPos(1, 1) |
| 251 | + | |
| 252 | -- Redraw screen after returning from shell | |
| 253 | drawTopBar() | |
| 254 | centerText(3, config.username .. "'s Lemonphone", config.accentColor) | |
| 255 | drawMenu(selected) | |
| 256 | end | |
| 257 | end | |
| 258 | ||
| 259 | -- Only redraw selection highlight if it changed | |
| 260 | if oldSelected ~= selected then | |
| 261 | drawMenu(selected) | |
| 262 | end | |
| 263 | ||
| 264 | elseif event == "timer" then | |
| 265 | -- Only redraw top bar (time + signal) | |
| 266 | drawTopBar() | |
| 267 | timer = os.startTimer(refreshRate) | |
| 268 | end | |
| 269 | end | |
| 270 |