mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2025-12-20 11:09:21 +01:00
Крч, формат пикч новый, парочка багфиксов, формат .app чутка изменен, и ваще прям мяу. Но багов может быть дохуя
939 lines
35 KiB
Lua
Executable File
939 lines
35 KiB
Lua
Executable File
|
||
---------------------------------------------- Libraries ------------------------------------------------------------------------
|
||
|
||
local component = require("component")
|
||
local computer = require("computer")
|
||
local event = require("event")
|
||
local advancedLua = require("advancedLua")
|
||
local image = require("image")
|
||
local buffer = require("doubleBuffering")
|
||
local GUI = require("GUI")
|
||
local ecs = require("ECSAPI")
|
||
local fs = require("filesystem")
|
||
local unicode = require("unicode")
|
||
local keyboard = require("keyboard")
|
||
|
||
---------------------------------------------- Core constants ------------------------------------------------------------------------
|
||
|
||
local MineOSCore = {}
|
||
|
||
MineOSCore.showApplicationIcons = true
|
||
MineOSCore.iconWidth = 12
|
||
MineOSCore.iconHeight = 6
|
||
MineOSCore.iconClickDelay = 0.2
|
||
|
||
MineOSCore.paths = {}
|
||
MineOSCore.paths.OS = "/MineOS/"
|
||
MineOSCore.paths.system = MineOSCore.paths.OS .. "System/"
|
||
MineOSCore.paths.wallpaper = MineOSCore.paths.system .. "OS/Wallpaper.lnk"
|
||
MineOSCore.paths.localizationFiles = MineOSCore.paths.system .. "OS/Languages/"
|
||
MineOSCore.paths.icons = MineOSCore.paths.system .. "OS/Icons/"
|
||
MineOSCore.paths.applications = MineOSCore.paths.OS .. "Applications/"
|
||
MineOSCore.paths.pictures = MineOSCore.paths.OS .. "Pictures/"
|
||
MineOSCore.paths.desktop = MineOSCore.paths.OS .. "Desktop/"
|
||
MineOSCore.paths.applicationList = MineOSCore.paths.system .. "OS/Applications.txt"
|
||
MineOSCore.paths.trash = MineOSCore.paths.OS .. "Trash/"
|
||
MineOSCore.paths.OSSettings = MineOSCore.paths.system .. "OS/OSSettings.cfg"
|
||
|
||
MineOSCore.sortingMethods = enum(
|
||
"type",
|
||
"name",
|
||
"date"
|
||
)
|
||
|
||
MineOSCore.localization = {}
|
||
|
||
|
||
---------------------------------------------- Tasks ------------------------------------------------------------------------
|
||
|
||
--[[
|
||
MineOSCore.tasks = {}
|
||
|
||
function MineOSCore.showTaskManager()
|
||
MineOSCore.tasks.current = 1
|
||
buffer.clear(0x2D2D2D)
|
||
for i = 1, #MineOSCore.tasks do
|
||
buffer.text(1, i, 0xFFFFFF, i .. ": " .. table.toString(MineOSCore.tasks[i]))
|
||
end
|
||
buffer.draw()
|
||
MineOSCore.rawPullSignal()
|
||
end
|
||
|
||
local function replacePullSignal()
|
||
MineOSCore.rawPullSignal = computer.pullSignal
|
||
computer.pullSignal = function(timeout)
|
||
local signalData = {MineOSCore.rawPullSignal(timeout)}
|
||
|
||
local i = 1
|
||
while i <= #MineOSCore.tasks do
|
||
if coroutine.status(MineOSCore.tasks[i].coroutine) == "dead" then
|
||
if i > 1 then
|
||
MineOSCore.tasks.current = 1
|
||
MineOSCore.tasks[i].coroutine = nil
|
||
table.remove(MineOSCore.tasks, i)
|
||
else
|
||
error("MineOSCore fatal error cyka bitch")
|
||
end
|
||
else
|
||
i = i + 1
|
||
end
|
||
end
|
||
|
||
if MineOSCore.taskManagerOpen then
|
||
MineOSCore.showTaskManager()
|
||
else
|
||
if keyboard.isKeyDown(0) and keyboard.isKeyDown(28) then
|
||
MineOSCore.taskManagerOpen = true
|
||
MineOSCore.tasks[MineOSCore.tasks.current].isPaused = true
|
||
computer.pushSignal("")
|
||
coroutine.yield()
|
||
else
|
||
return table.unpack(signalData)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function MineOSCore.newTask(func, name)
|
||
local task = {
|
||
coroutine = coroutine.create(func),
|
||
name = name,
|
||
isPaused = false
|
||
}
|
||
table.insert(MineOSCore.tasks, task)
|
||
return task
|
||
end
|
||
|
||
function MineOSCore.newTaskFromFile(path)
|
||
local loadSuccess, loadReason = loadfile(path)
|
||
MineOSCore.newTask(loadSuccess)
|
||
end
|
||
|
||
]]
|
||
|
||
---------------------------------------------- Current sсript processing methods ------------------------------------------------------------------------
|
||
|
||
function MineOSCore.getCurrentScriptDirectory()
|
||
return MineOSCore.getFilePath(getCurrentScript())
|
||
end
|
||
|
||
function MineOSCore.getCurrentApplicationResourcesDirectory()
|
||
return MineOSCore.getCurrentScriptDirectory() .. "/Resources/"
|
||
end
|
||
|
||
function MineOSCore.getLocalization(pathToLocalizationFolder)
|
||
local localizationFileName = pathToLocalizationFolder .. _G.OSSettings.language .. ".lang"
|
||
if fs.exists(localizationFileName) then
|
||
return table.fromFile(localizationFileName)
|
||
else
|
||
error("Localization file \"" .. localizationFileName .. "\" doesn't exists")
|
||
end
|
||
end
|
||
|
||
function MineOSCore.getCurrentApplicationLocalization()
|
||
return MineOSCore.getLocalization(MineOSCore.getCurrentApplicationResourcesDirectory() .. "Localization/")
|
||
end
|
||
|
||
function MineOSCore.getMethodExecutionTime(method)
|
||
local oldOSClock = os.clock()
|
||
method()
|
||
return os.clock() - oldOSClock
|
||
end
|
||
|
||
function MineOSCore.getAverageMethodExecutionTime(method, countOfTries)
|
||
local averageTime = 0
|
||
for i = 1, countOfTries do
|
||
averageTime = (averageTime + MineOSCore.getMethodExecutionTime(method)) / 2
|
||
os.sleep(0.1)
|
||
end
|
||
return averageTime
|
||
end
|
||
|
||
---------------------------------------------- Filesystem-related methods ------------------------------------------------------------------------
|
||
|
||
local function getFilenameAndFormat(path)
|
||
local fileName, format = string.match(path, "^(.+)(%.[^%/]+)%/?$")
|
||
return (fileName or path), format
|
||
end
|
||
|
||
function MineOSCore.getFilePath(path)
|
||
return string.match(path, "^(.+%/).") or ""
|
||
end
|
||
|
||
function MineOSCore.getFileName(path)
|
||
return string.match(path, "%/?([^%/]+)%/?$")
|
||
end
|
||
|
||
function MineOSCore.getFileFormat(path)
|
||
local fileName, format = getFilenameAndFormat(path)
|
||
return format
|
||
end
|
||
|
||
function MineOSCore.hideFileFormat(path)
|
||
local fileName, format = getFilenameAndFormat(path)
|
||
return fileName
|
||
end
|
||
|
||
function MineOSCore.isFileHidden(path)
|
||
if string.match(path, "^%..+$") then return true end
|
||
return false
|
||
end
|
||
|
||
function MineOSCore.getFileList(path)
|
||
if not fs.exists(path) then error("Failed to get file list: directory \"" .. tostring(path) .. "\" doesn't exists") end
|
||
if not fs.isDirectory(path) then error("Failed to get file list: path \"" .. tostring(path) .. "\" is not a directory") end
|
||
|
||
local fileList = {}
|
||
for file in fs.list(path) do table.insert(fileList, file) end
|
||
return fileList
|
||
end
|
||
|
||
function MineOSCore.sortFileList(path, fileList, sortingMethod, showHiddenFiles)
|
||
local sortedFileList = {}
|
||
|
||
if sortingMethod == MineOSCore.sortingMethods.type then
|
||
local typeList = {}
|
||
for i = 1, #fileList do
|
||
local fileFormat = MineOSCore.getFileFormat(fileList[i]) or "Script"
|
||
if fs.isDirectory(path .. fileList[i]) and fileFormat ~= ".app" then fileFormat = "Folder" end
|
||
typeList[fileFormat] = typeList[fileFormat] or {}
|
||
table.insert(typeList[fileFormat], fileList[i])
|
||
end
|
||
|
||
if typeList.Folder then
|
||
for i = 1, #typeList.Folder do
|
||
table.insert(sortedFileList, typeList.Folder[i])
|
||
end
|
||
typeList["Folder"] = nil
|
||
end
|
||
|
||
for fileFormat in pairs(typeList) do
|
||
for i = 1, #typeList[fileFormat] do
|
||
table.insert(sortedFileList, typeList[fileFormat][i])
|
||
end
|
||
end
|
||
elseif MineOSCore.sortingMethods.name then
|
||
sortedFileList = fileList
|
||
elseif MineOSCore.sortingMethods.date then
|
||
for i = 1, #fileList do
|
||
fileList[i] = {fileList[i], fs.lastModified(path .. fileList[i])}
|
||
end
|
||
table.sort(fileList, function(a,b) return a[2] > b[2] end)
|
||
for i = 1, #fileList do
|
||
table.insert(sortedFileList, fileList[i][1])
|
||
end
|
||
else
|
||
error("Unknown sorting method: " .. tostring(sortingMethod))
|
||
end
|
||
|
||
local i = 1
|
||
while i <= #sortedFileList do
|
||
if not showHiddenFiles and MineOSCore.isFileHidden(sortedFileList[i]) then
|
||
table.remove(sortedFileList, i)
|
||
else
|
||
i = i + 1
|
||
end
|
||
end
|
||
|
||
return sortedFileList
|
||
end
|
||
|
||
function MineOSCore.limitFileName(text, limit)
|
||
if unicode.len(text) > limit then
|
||
local partSize = math.ceil(limit / 2)
|
||
text = unicode.sub(text, 1, partSize) .. "…" .. unicode.sub(text, -partSize + 1, -1)
|
||
end
|
||
return text
|
||
end
|
||
|
||
function MineOSCore.getFolderSize(path)
|
||
local size = 0
|
||
for file in fs.list(path) do
|
||
if fs.isDirectory(path .. file) then
|
||
size = size + MineOSCore.getFolderSize(path .. file)
|
||
else
|
||
size = size + fs.size(path .. file)
|
||
end
|
||
end
|
||
return size
|
||
end
|
||
|
||
---------------------------------------------- MineOS Icons related methods ------------------------------------------------------------------------
|
||
|
||
function MineOSCore.saveOSSettings()
|
||
table.toFile(MineOSCore.paths.OSSettings, _G.OSSettings, true)
|
||
end
|
||
|
||
function MineOSCore.loadOSSettings()
|
||
_G.OSSettings = table.fromFile(MineOSCore.paths.OSSettings)
|
||
end
|
||
|
||
function MineOSCore.loadIcon(name, path)
|
||
if not MineOSCore.icons[name] then MineOSCore.icons[name] = image.load(path) end
|
||
return MineOSCore.icons[name]
|
||
end
|
||
|
||
--Вся необходимая информация для иконок
|
||
function MineOSCore.loadStandartIcons()
|
||
MineOSCore.icons = {}
|
||
MineOSCore.loadIcon("folder", MineOSCore.paths.icons .. "Folder.pic")
|
||
MineOSCore.loadIcon("script", MineOSCore.paths.icons .. "Script.pic")
|
||
MineOSCore.loadIcon("text", MineOSCore.paths.icons .. "Text.pic")
|
||
MineOSCore.loadIcon("config", MineOSCore.paths.icons .. "Config.pic")
|
||
MineOSCore.loadIcon("lua", MineOSCore.paths.icons .. "Lua.pic")
|
||
MineOSCore.loadIcon("image", MineOSCore.paths.icons .. "Image.pic")
|
||
MineOSCore.loadIcon("pastebin", MineOSCore.paths.icons .. "Pastebin.pic")
|
||
MineOSCore.loadIcon("fileNotExists", MineOSCore.paths.icons .. "FileNotExists.pic")
|
||
MineOSCore.loadIcon("archive", MineOSCore.paths.icons .. "Archive.pic")
|
||
MineOSCore.loadIcon("model3D", MineOSCore.paths.icons .. "3DModel.pic")
|
||
MineOSCore.loadIcon("application", MineOSCore.paths.icons .. "Application.pic")
|
||
MineOSCore.loadIcon("trash", MineOSCore.paths.icons .. "Trash.pic")
|
||
end
|
||
|
||
function MineOSCore.init()
|
||
MineOSCore.loadOSSettings()
|
||
MineOSCore.localization = table.fromFile(MineOSCore.paths.localizationFiles .. _G.OSSettings.language .. ".lang")
|
||
MineOSCore.loadStandartIcons()
|
||
end
|
||
|
||
function MineOSCore.waitForPressingAnyKey()
|
||
print(" ")
|
||
print(MineOSCore.localization.pressAnyKeyToContinue)
|
||
while true do
|
||
local eventType = event.pull()
|
||
if eventType == "key_down" or eventType == "touch" then break end
|
||
end
|
||
end
|
||
|
||
function MineOSCore.analyzeIconFormat(iconObject)
|
||
if iconObject.isDirectory then
|
||
if iconObject.format == ".app" then
|
||
if MineOSCore.showApplicationIcons then
|
||
if fs.exists(iconObject.path .. "/Resources/Icon.pic") then
|
||
iconObject.iconImage.image = image.load(iconObject.path .. "/Resources/Icon.pic")
|
||
else
|
||
iconObject.iconImage.image = MineOSCore.icons.fileNotExists
|
||
end
|
||
else
|
||
iconObject.iconImage.image = MineOSCore.icons.application
|
||
end
|
||
|
||
iconObject.launch = function()
|
||
ecs.applicationHelp(iconObject.path)
|
||
if fs.exists(iconObject.path .. "/Main.lua") then
|
||
MineOSCore.safeLaunch(iconObject.path .. "/Main.lua")
|
||
else
|
||
MineOSCore.safeLaunch(iconObject.path .. "/" .. MineOSCore.hideFileFormat(MineOSCore.getFileName(iconObject.path)) .. ".lua")
|
||
end
|
||
end
|
||
else
|
||
iconObject.iconImage.image = MineOSCore.icons.folder
|
||
iconObject.launch = function()
|
||
computer.pushSignal("MineOSCore", "changeWorkpath", iconObject.path)
|
||
end
|
||
end
|
||
else
|
||
if iconObject.format == ".lnk" then
|
||
iconObject.shortcutPath = ecs.readShortcut(iconObject.path)
|
||
iconObject.shortcutFormat = MineOSCore.getFileFormat(iconObject.shortcutPath)
|
||
iconObject.shortcutIsDirectory = fs.isDirectory(iconObject.shortcutPath)
|
||
iconObject.isShortcut = true
|
||
|
||
local shortcutIconObject = MineOSCore.analyzeIconFormat({
|
||
path = iconObject.shortcutPath,
|
||
format = iconObject.shortcutFormat,
|
||
isDirectory = iconObject.shortcutIsDirectory,
|
||
iconImage = iconObject.iconImage
|
||
})
|
||
|
||
iconObject.iconImage.image = shortcutIconObject.iconImage.image
|
||
iconObject.launch = shortcutIconObject.launch
|
||
|
||
shortcutIconObject = nil
|
||
elseif iconObject.format == ".cfg" or iconObject.format == ".config" then
|
||
iconObject.iconImage.image = MineOSCore.icons.config
|
||
iconObject.launch = function()
|
||
MineOSCore.safeLaunch(MineOSCore.paths.applications .. "/MineCode IDE.app/MineCode IDE.lua", "open", iconObject.path)
|
||
end
|
||
elseif iconObject.format == ".txt" or iconObject.format == ".rtf" then
|
||
iconObject.iconImage.image = MineOSCore.icons.text
|
||
iconObject.launch = function()
|
||
MineOSCore.safeLaunch(MineOSCore.paths.applications .. "/MineCode IDE.app/MineCode IDE.lua", "open", iconObject.path)
|
||
end
|
||
elseif iconObject.format == ".lua" then
|
||
iconObject.iconImage.image = MineOSCore.icons.lua
|
||
iconObject.launch = function()
|
||
ecs.prepareToExit()
|
||
if MineOSCore.safeLaunch(iconObject.path) then
|
||
MineOSCore.waitForPressingAnyKey()
|
||
end
|
||
end
|
||
elseif iconObject.format == ".pic" or iconObject.format == ".png" then
|
||
iconObject.iconImage.image = MineOSCore.icons.image
|
||
iconObject.launch = function()
|
||
MineOSCore.safeLaunch(MineOSCore.paths.applications .. "Photoshop.app/Photoshop.lua", "open", iconObject.path)
|
||
end
|
||
elseif iconObject.format == ".pkg" then
|
||
iconObject.iconImage.image = MineOSCore.icons.archive
|
||
iconObject.launch = function()
|
||
require("compressor").unpack(iconObject.path, MineOSCore.getFilePath(iconObject.path))
|
||
end
|
||
elseif iconObject.format == ".3dm" then
|
||
iconObject.iconImage.image = MineOSCore.icons.model3D
|
||
iconObject.launch = function()
|
||
MineOSCore.safeLaunch(MineOSCore.paths.applications .. "3DPrint.app/3DPrint.lua", "open", iconObject.path)
|
||
end
|
||
elseif not fs.exists(iconObject.path) then
|
||
iconObject.iconImage.image = MineOSCore.icons.fileNotExists
|
||
iconObject.launch = function()
|
||
GUI.error("Application is corrupted")
|
||
end
|
||
else
|
||
iconObject.iconImage.image = MineOSCore.icons.script
|
||
iconObject.launch = function()
|
||
ecs.prepareToExit()
|
||
if MineOSCore.safeLaunch(iconObject.path) then
|
||
MineOSCore.waitForPressingAnyKey()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return iconObject
|
||
end
|
||
|
||
function MineOSCore.getParametersForDrawingIcons(fieldWidth, fieldHeight, xSpaceBetweenIcons, ySpaceBetweenIcons)
|
||
local xCountOfIcons, yCountOfIcons = math.floor(fieldWidth / (MineOSCore.iconWidth + xSpaceBetweenIcons)), math.floor(fieldHeight / (MineOSCore.iconHeight + ySpaceBetweenIcons))
|
||
local totalCountOfIcons = xCountOfIcons * yCountOfIcons
|
||
return xCountOfIcons, yCountOfIcons, totalCountOfIcons
|
||
end
|
||
|
||
function MineOSCore.createIconObject(x, y, path, textColor, showFileFormat)
|
||
local iconObject = GUI.container(x, y, MineOSCore.iconWidth, MineOSCore.iconHeight)
|
||
|
||
iconObject.path = path
|
||
iconObject.size = fs.size(iconObject.path)
|
||
iconObject.isDirectory = fs.isDirectory(iconObject.path)
|
||
iconObject.format = MineOSCore.getFileFormat(iconObject.path)
|
||
iconObject.showFormat = showFileFormat
|
||
iconObject.isShortcut = false
|
||
iconObject.isSelected = false
|
||
|
||
iconObject.iconImage = iconObject:addImage(3, 1, {8, 4})
|
||
iconObject.textLabel = iconObject:addLabel(1, MineOSCore.iconHeight, MineOSCore.iconWidth, 1, textColor, MineOSCore.getFileName(iconObject.path)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)
|
||
|
||
local oldDraw = iconObject.draw
|
||
iconObject.draw = function(iconObject)
|
||
if iconObject.isSelected then buffer.square(iconObject.x, iconObject.y, iconObject.width, iconObject.height, 0xFFFFFF, 0x000000, " ", 50) end
|
||
if iconObject.showFormat then
|
||
iconObject.textLabel.text = MineOSCore.limitFileName(MineOSCore.getFileName(iconObject.path), iconObject.textLabel.width)
|
||
else
|
||
iconObject.textLabel.text = MineOSCore.limitFileName(MineOSCore.hideFileFormat(MineOSCore.getFileName(iconObject.path)), iconObject.textLabel.width)
|
||
end
|
||
oldDraw(iconObject)
|
||
if iconObject.isShortcut then buffer.set(iconObject.iconImage.x + iconObject.iconImage.width - 1, iconObject.iconImage.y + iconObject.iconImage.height - 1, 0xFFFFFF, 0x000000, "<") end
|
||
end
|
||
|
||
-- Поддержка изменяемых извне функций правого и левого кликов
|
||
iconObject.onLeftClick = MineOSCore.iconLeftClick
|
||
iconObject.onRightClick = MineOSCore.iconRightClick
|
||
|
||
-- Обработка клика непосредственно на иконку
|
||
iconObject.iconImage.onTouch = function(eventData)
|
||
iconObject.isSelected = true
|
||
local firstParent = iconObject:getFirstParent()
|
||
firstParent:draw()
|
||
buffer.draw()
|
||
|
||
if eventData[5] == 0 then
|
||
os.sleep(MineOSCore.iconClickDelay)
|
||
iconObject.onLeftClick(iconObject, eventData)
|
||
else
|
||
iconObject.onRightClick(iconObject, eventData)
|
||
end
|
||
|
||
iconObject.isSelected = false
|
||
firstParent:draw()
|
||
buffer.draw()
|
||
end
|
||
|
||
-- Онализ формата и прочего говна иконки для последующего получения изображения иконки и функции-лаунчера
|
||
MineOSCore.analyzeIconFormat(iconObject)
|
||
|
||
return iconObject
|
||
end
|
||
|
||
local function updateIconFieldFileList(iconField)
|
||
iconField.fileList = MineOSCore.getFileList(iconField.workpath)
|
||
iconField.fileList = MineOSCore.sortFileList(iconField.workpath, iconField.fileList, iconField.sortingMethod, iconField.showHiddenFiles)
|
||
iconField.children = {}
|
||
|
||
local xPos, yPos, counter = 1, 1, 1
|
||
for i = iconField.fromFile, iconField.fromFile + iconField.iconCount.total - 1 do
|
||
if not iconField.fileList[i] then break end
|
||
|
||
iconField:addChild(
|
||
MineOSCore.createIconObject(
|
||
xPos, yPos, iconField.workpath .. iconField.fileList[i], iconField.colors.iconText, iconField.showFileFormat
|
||
),
|
||
GUI.objectTypes.container
|
||
)
|
||
|
||
xPos, counter = xPos + MineOSCore.iconWidth + iconField.spaceBetweenIcons.x, counter + 1
|
||
if counter > iconField.iconCount.width then
|
||
xPos, counter = 1, 1
|
||
yPos = yPos + MineOSCore.iconHeight + iconField.spaceBetweenIcons.y
|
||
end
|
||
end
|
||
|
||
return iconField
|
||
end
|
||
|
||
function MineOSCore.createIconField(x, y, width, height, xCountOfIcons, yCountOfIcons, totalCountOfIcons, xSpaceBetweenIcons, ySpaceBetweenIcons, iconTextColor, showFileFormat, showHiddenFiles, sortingMethod, workpathworkpath)
|
||
local iconField = GUI.container(x, y, width, height)
|
||
|
||
iconField.colors = {iconText = iconTextColor}
|
||
|
||
iconField.iconCount = {}
|
||
iconField.spaceBetweenIcons = {x = xSpaceBetweenIcons, y = ySpaceBetweenIcons}
|
||
iconField.iconCount.width, iconField.iconCount.height, iconField.iconCount.total = xCountOfIcons, yCountOfIcons, totalCountOfIcons
|
||
|
||
iconField.workpath = workpath
|
||
iconField.showFileFormat = showFileFormat
|
||
iconField.showHiddenFiles = showHiddenFiles
|
||
iconField.sortingMethod = sortingMethod
|
||
iconField.fileList = {}
|
||
iconField.fromFile = fromFile
|
||
|
||
iconField.updateFileList = updateIconFieldFileList
|
||
|
||
return iconField
|
||
end
|
||
|
||
-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
--Функция парсинга Lua-сообщения об ошибке. Конвертирует из строки в массив.
|
||
function MineOSCore.parseErrorMessage(error, indentationWidth)
|
||
local parsedError = {}
|
||
|
||
--Замена /r/n и табсов
|
||
error = string.gsub(error, "\r\n", "\n")
|
||
error = string.gsub(error, " ", string.rep(" ", indentationWidth or 4))
|
||
|
||
--Удаление энтеров
|
||
local searchFrom, starting, ending = 1
|
||
for i = 1, unicode.len(error) do
|
||
starting, ending = string.find(error, "\n", searchFrom)
|
||
if starting then
|
||
table.insert(parsedError, unicode.sub(error, searchFrom, starting - 1))
|
||
searchFrom = ending + 1
|
||
else
|
||
break
|
||
end
|
||
end
|
||
|
||
--На всякий случай, если сообщение об ошибке без энтеров вообще, т.е. однострочное
|
||
if #parsedError == 0 then table.insert(parsedError, error) end
|
||
|
||
return parsedError
|
||
end
|
||
|
||
local function drawErrorWindow(path, programVersion, errorLine, reason)
|
||
local oldDrawLimit = buffer.getDrawLimit(); buffer.resetDrawLimit()
|
||
local width, height = buffer.screen.width, math.floor(buffer.screen.height * 0.45)
|
||
local y = math.floor(buffer.screen.height / 2 - height / 2)
|
||
|
||
-- Окошечко и всякая шняжка на нем
|
||
local window = GUI.window(1, y, width, height, width, height)
|
||
window:addPanel(1, 1, width, 3, 0x383838)
|
||
window:addLabel(1, 2, width, 1, 0xFFFFFF, MineOSCore.localization.errorWhileRunningProgram .. "\"" .. MineOSCore.getFileName(path) .. "\""):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)
|
||
local windowActionButtons = window:addWindowActionButtons(2, 2, false)
|
||
local sendToDeveloperButton = window:addAdaptiveButton(9, 1, 2, 1, 0x444444, 0xFFFFFF, 0x343434, 0xFFFFFF, MineOSCore.localization.sendFeedback)
|
||
|
||
--Кодик на окошечке
|
||
local lines = {}
|
||
local fromLine = errorLine - math.floor((height - 3) / 2) + 1; if fromLine <= 0 then fromLine = 1 end
|
||
local toLine = fromLine + window.height - 3 - 1
|
||
local file = io.open(path, "r")
|
||
local lineCounter = 1
|
||
for line in file:lines() do
|
||
if lineCounter >= fromLine and lineCounter <= toLine then
|
||
lines[lineCounter] = string.gsub(line, " ", " ")
|
||
elseif lineCounter < fromLine then
|
||
lines[lineCounter] = " "
|
||
elseif lineCounter > toLine then
|
||
break
|
||
end
|
||
lineCounter = lineCounter + 1
|
||
end
|
||
file:close()
|
||
|
||
local codeView = window:addCodeView(1, 4, math.floor(width * 0.62), height - 3, lines, 1, fromLine, 100, {}, {[errorLine] = 0xFF4444}, true, 2)
|
||
codeView.scrollBars.horizontal.isHidden = true
|
||
|
||
-- Текстбоксик
|
||
local stackTextBox = window:addTextBox(codeView.width + 1, 4, window.width - codeView.width, codeView.height, 0xFFFFFF, 0x000000, string.wrap(MineOSCore.parseErrorMessage(reason, 4), window.width - codeView.width - 2), 1, 1, 0)
|
||
|
||
-- Всякие действия пиздатые
|
||
local function exit()
|
||
windowActionButtons.close:pressAndRelease()
|
||
buffer.setDrawLimit(oldDrawLimit)
|
||
window:close()
|
||
end
|
||
|
||
windowActionButtons.close.onTouch = exit
|
||
|
||
window.onDrawStarted = function()
|
||
buffer.clear(0x000000, 50)
|
||
end
|
||
|
||
window.onKeyDown = function(eventData)
|
||
if eventData[4] == 28 then exit() end
|
||
end
|
||
|
||
sendToDeveloperButton.onTouch = function()
|
||
local data = ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true,
|
||
{"EmptyLine"},
|
||
{"CenterText", 0x880000, MineOSCore.localization.sendFeedback},
|
||
{"EmptyLine"},
|
||
{"Input", 0x262626, 0x880000, MineOSCore.localization.yourContacts},
|
||
{"Input", 0x262626, 0x880000, MineOSCore.localization.additionalInfo},
|
||
{"EmptyLine"},
|
||
{"CenterText", 0x880000, MineOSCore.localization.stackTraceback .. ":"},
|
||
{"EmptyLine"},
|
||
{"TextField", 5, 0xFFFFFF, 0x000000, 0xcccccc, 0x3366CC, reason},
|
||
{"Button", {0x999999, 0xffffff, "OK"}, {0x777777, 0xffffff, MineOSCore.localization.cancel}}
|
||
)
|
||
|
||
if data[3] == "OK" then
|
||
if component.isAvailable("internet") then
|
||
local url = "https://api.mcmodder.ru/ECS/report.php?path=" .. path .. "&version=" .. string.optimizeForURLRequests(programVersion) .. "&userContacts=" .. string.optimizeForURLRequests(data[1]) .. "&userMessage=" .. string.optimizeForURLRequests(data[2]) .. "&errorMessage=" .. string.optimizeForURLRequests(reason)
|
||
local success, reason = component.internet.request(url)
|
||
if success then
|
||
success:close()
|
||
else
|
||
ecs.error(reason)
|
||
end
|
||
end
|
||
exit()
|
||
end
|
||
end
|
||
|
||
-- Начинаем гомоеблю!
|
||
window:draw()
|
||
buffer.draw()
|
||
for i = 1, 3 do component.computer.beep(1500, 0.08) end
|
||
window:handleEvents()
|
||
end
|
||
|
||
function MineOSCore.safeLaunch(path, ...)
|
||
local args = {...}
|
||
local oldResolutionWidth, oldResolutionHeight = buffer.screen.width, buffer.screen.height
|
||
local finalSuccess, finalPath, finalLine, finalTraceback = true
|
||
|
||
if fs.exists(path) then
|
||
local loadSuccess, loadReason = loadfile(string.canonicalPath("/" .. path))
|
||
|
||
if loadSuccess then
|
||
local function launchMethod()
|
||
loadSuccess(table.unpack(args))
|
||
end
|
||
|
||
local function tracebackMethod(xpcallTraceback)
|
||
local traceback, info, firstMatch = tostring(xpcallTraceback) .. "\n" .. debug.traceback()
|
||
for runLevel = 0, math.huge do
|
||
info = debug.getinfo(runLevel)
|
||
if info then
|
||
if (info.what == "main" or info.what == "Lua") and info.source ~= "=machine" then
|
||
if firstMatch then
|
||
return {
|
||
path = info.source:sub(2, -1),
|
||
line = info.currentline,
|
||
traceback = traceback
|
||
}
|
||
else
|
||
firstMatch = true
|
||
end
|
||
end
|
||
else
|
||
error("Failed to get debug info for runlevel " .. runLevel)
|
||
end
|
||
end
|
||
end
|
||
|
||
local runSuccess, runReason = xpcall(launchMethod, tracebackMethod)
|
||
if type(runReason) == "string" then
|
||
GUI.error(runReason, {title = {color = 0xFFDB40, text = "Warning"}})
|
||
else
|
||
if not runSuccess and not string.match(runReason.traceback, "^table") and not string.find(runReason.traceback, "interrupted", 1, 15) then
|
||
finalSuccess, finalPath, finalLine, finalTraceback = false, runReason.path, runReason.line, runReason.traceback
|
||
end
|
||
end
|
||
else
|
||
finalSuccess, finalPath, finalTraceback = false, path, loadReason
|
||
local match = string.match(loadReason, ":(%d+)%:")
|
||
finalLine = tonumber(match)
|
||
if not match or not finalLine then error("Дебажь говно! " .. tostring(loadReason)) end
|
||
end
|
||
else
|
||
GUI.error("Failed to safely launch file that doesn't exists: \"" .. path .. "\"", {title = {color = 0xFFDB40, text = "Warning"}})
|
||
end
|
||
|
||
if not finalSuccess then
|
||
drawErrorWindow(finalPath, "1.0", finalLine, finalTraceback)
|
||
end
|
||
|
||
component.screen.setPrecise(false)
|
||
component.gpu.setResolution(oldResolutionWidth, oldResolutionHeight)
|
||
buffer.start()
|
||
|
||
return finalSuccess, finalPath, finalLine, finalTraceback
|
||
end
|
||
|
||
-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
function MineOSCore.iconLeftClick(iconObject, eventData)
|
||
iconObject.launch()
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
end
|
||
|
||
function MineOSCore.iconRightClick(icon, eventData)
|
||
local action
|
||
-- Разные контекстные меню
|
||
if icon.isDirectory then
|
||
if icon.format == ".app" then
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuShowPackageContent},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
{MineOSCore.localization.contextMenuCreateShortcut, icon.format == ".lnk"},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuArchive},
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
else
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
{MineOSCore.localization.contextMenuCreateShortcut, icon.format == ".lnk"},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuArchive},
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
end
|
||
else
|
||
if icon.isShortcut then
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuEdit},
|
||
{MineOSCore.localization.contextMenuShowContainingFolder},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
elseif icon.format == ".pic" then
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuSetAsWallpaper},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
{MineOSCore.localization.contextMenuCreateShortcut, icon.format == ".lnk"},
|
||
"-",
|
||
-- {MineOSCore.localization.contextMenuArchive},
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
elseif icon.format == ".lua" then
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuEdit},
|
||
{MineOSCore.localization.contextMenuFlashEEPROM, (not component.isAvailable("eeprom") or icon.size > 4096)},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
{MineOSCore.localization.contextMenuCreateShortcut, icon.format == ".lnk"},
|
||
-- "-",
|
||
-- {MineOSCore.localization.contextMenuUploadToPastebin, true},
|
||
"-",
|
||
-- {MineOSCore.localization.contextMenuArchive},
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
else
|
||
action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuEdit},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuCut},
|
||
{MineOSCore.localization.contextMenuCopy},
|
||
{MineOSCore.localization.contextMenuRename},
|
||
{MineOSCore.localization.contextMenuCreateShortcut, icon.format == ".lnk"},
|
||
"-",
|
||
-- {MineOSCore.localization.contextMenuArchive},
|
||
{MineOSCore.localization.contextMenuAddToDock},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuProperties},
|
||
{MineOSCore.localization.contextMenuDelete}
|
||
):show()
|
||
end
|
||
end
|
||
|
||
if action == MineOSCore.localization.contextMenuEdit then
|
||
MineOSCore.safeLaunch(MineOSCore.paths.applications .. "/MineCode IDE.app/MineCode IDE.lua", "open", icon.path)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == "Свойства" then
|
||
MineOSCore.showPropertiesWindow(eventData[3], eventData[4], 40, icon)
|
||
elseif action == MineOSCore.localization.contextMenuShowContainingFolder then
|
||
computer.pushSignal("MineOSCore", "changeWorkpath", MineOSCore.getFilePath(icon.shortcutPath))
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuAddToFavourites then
|
||
computer.pushSignal("finderFavouriteAdded", icon.path)
|
||
elseif action == MineOSCore.localization.contextMenuShowPackageContent then
|
||
computer.pushSignal("MineOSCore", "changeWorkpath", icon.path)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuCopy then
|
||
_G.clipboard = icon.path
|
||
elseif action == MineOSCore.localization.contextMenuCut then
|
||
_G.clipboard = icon.path
|
||
_G.clipboardCut = true
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuDelete then
|
||
if MineOSCore.getFilePath(icon.path) == MineOSCore.paths.trash then
|
||
fs.remove(icon.path)
|
||
else
|
||
local newName = MineOSCore.paths.trash .. MineOSCore.getFileName(icon.path)
|
||
local clearName = MineOSCore.hideFileFormat(MineOSCore.getFileName(icon.path))
|
||
local repeats = 1
|
||
while fs.exists(newName) do
|
||
newName, repeats = MineOSCore.paths.trash .. clearName .. string.rep("-copy", repeats) .. icon.format, repeats + 1
|
||
end
|
||
fs.rename(icon.path, newName)
|
||
end
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuRename then
|
||
ecs.rename(icon.path)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuCreateShortcut then
|
||
ecs.createShortCut(MineOSCore.getFilePath(icon.path) .. "/" .. ecs.hideFileFormat(MineOSCore.getFileName(icon.path)) .. ".lnk", icon.path)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuArchive then
|
||
require("compressor").pack(MineOSCore.getFilePath(icon.path) .. MineOSCore.hideFileFormat(MineOSCore.getFileName(icon.path)) .. ".pkg", icon.path)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuSetAsWallpaper then
|
||
fs.remove(MineOSCore.paths.wallpaper)
|
||
ecs.createShortCut(MineOSCore.paths.wallpaper, icon.path)
|
||
computer.pushSignal("MineOSCore", "updateWallpaper")
|
||
elseif action == MineOSCore.localization.contextMenuFlashEEPROM then
|
||
local file = io.open(icon.path, "r")
|
||
component.eeprom.set(file:read("*a"))
|
||
file:close()
|
||
computer.beep(1500, 0.2)
|
||
elseif action == MineOSCore.localization.contextMenuCreateApplication then
|
||
ecs.newApplicationFromLuaFile(icon.path, MineOSCore.getFilePath(icon.path) or "")
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuAddToDock then
|
||
table.insert(_G.OSSettings.dockShortcuts, {path = icon.path})
|
||
MineOSCore.saveOSSettings()
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
end
|
||
end
|
||
|
||
function MineOSCore.emptyZoneClick(eventData, workspace, workpath)
|
||
local action = GUI.contextMenu(eventData[3], eventData[4],
|
||
{MineOSCore.localization.contextMenuNewFile},
|
||
{MineOSCore.localization.contextMenuNewFolder},
|
||
{MineOSCore.localization.contextMenuNewApplication},
|
||
"-",
|
||
{MineOSCore.localization.contextMenuPaste, (_G.clipboard == nil)}
|
||
):show()
|
||
|
||
if action == MineOSCore.localization.contextMenuNewFile then
|
||
ecs.newFile(workpath)
|
||
computer.pushSignal("MineOSCore", "updateFileListAndBufferTrueRedraw")
|
||
elseif action == MineOSCore.localization.contextMenuNewFolder then
|
||
ecs.newFolder(workpath)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuPaste then
|
||
ecs.copy(_G.clipboard, workpath)
|
||
if _G.clipboardCut then
|
||
fs.remove(_G.clipboard)
|
||
_G.clipboardCut = nil
|
||
_G.clipboard = nil
|
||
end
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
elseif action == MineOSCore.localization.contextMenuNewApplication then
|
||
ecs.newApplication(workpath)
|
||
computer.pushSignal("MineOSCore", "updateFileList")
|
||
end
|
||
end
|
||
|
||
local function addKeyAndValue(window, x, y, key, value)
|
||
window:addLabel(x, y, window.width , 1, 0x333333, key .. ":"); x = x + unicode.len(key) + 2
|
||
return window:addLabel(x, y, window.width, 1, 0x555555, value)
|
||
end
|
||
|
||
function MineOSCore.showPropertiesWindow(x, y, width, iconObject)
|
||
local window = GUI.window(x, y, width, 1)
|
||
local backgroundPanel = window:addPanel(1, 2, window.width, 1, 0xDDDDDD)
|
||
window:addPanel(1, 1, window.width, 1, 0xEEEEEE)
|
||
window:addLabel(1, 1, window.width, 1, 0x333333, MineOSCore.localization.contextMenuProperties):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)
|
||
window:addButton(2, 1, 1, 1, nil, 0xFF4940, nil, 0x992400, "●").onTouch = function() window:close() end
|
||
|
||
window:addImage(3, 3, iconObject.iconImage.image)
|
||
|
||
local y = 3
|
||
addKeyAndValue(window, 13, y, MineOSCore.localization.type, iconObject.format and iconObject.format or (iconObject.isDirectory and MineOSCore.localization.folder or MineOSCore.localization.unknown)); y = y + 1
|
||
local fileSizeLabel = addKeyAndValue(window, 13, y, MineOSCore.localization.size, iconObject.isDirectory and MineOSCore.localization.calculatingSize or string.format("%.2f", iconObject.size / 1024) .. " KB"); y = y + 1
|
||
addKeyAndValue(window, 13, y, MineOSCore.localization.date, os.date("%d.%m.%y, %H:%M", fs.lastModified(iconObject.path))); y = y + 1
|
||
addKeyAndValue(window, 13, y, MineOSCore.localization.path, " ")
|
||
|
||
local lines = string.wrap(iconObject.path, window.width - 19)
|
||
local textBox = window:addTextBox(19, y, window.width - 19, #lines, nil, 0x555555, lines, 1)
|
||
window.height = textBox.y + textBox.height
|
||
backgroundPanel.height = window.height - 1
|
||
|
||
if window.x + window.width > buffer.screen.width then window.x = window.x - (window.x + window.width - buffer.screen.width) end
|
||
if window.y + window.height > buffer.screen.height then window.y = window.y - (window.y + window.height - buffer.screen.height) end
|
||
|
||
window:draw()
|
||
buffer.draw()
|
||
|
||
if iconObject.isDirectory then
|
||
fileSizeLabel.text = string.format("%.2f", MineOSCore.getFolderSize(iconObject.path) / 1024) .. " KB"
|
||
window:draw()
|
||
buffer.draw()
|
||
end
|
||
|
||
window:handleEvents()
|
||
end
|
||
|
||
-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
MineOSCore.init()
|
||
|
||
-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
return MineOSCore
|
||
|
||
|
||
|
||
|
||
|