2020-09-02 14:09:27 +00:00
|
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
2017-04-02 06:50:24 +00:00
|
|
|
local DataStorage = require("datastorage")
|
|
|
|
local Font = require("ui/font")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
2020-09-02 14:09:27 +00:00
|
|
|
local LuaSettings = require("luasettings")
|
|
|
|
local Menu = require("ui/widget/menu")
|
|
|
|
local Screen = require("device").screen
|
|
|
|
local T = require("ffi/util").template
|
2020-03-20 22:41:36 +00:00
|
|
|
local TextViewer = require("ui/widget/textviewer")
|
|
|
|
local Trapper = require("ui/trapper")
|
2017-04-02 06:50:24 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local logger = require("logger")
|
|
|
|
local util = require("ffi/util")
|
|
|
|
local _ = require("gettext")
|
|
|
|
|
|
|
|
local Terminal = WidgetContainer:new{
|
|
|
|
name = "terminal",
|
2017-04-14 19:12:28 +00:00
|
|
|
command = "",
|
2020-09-02 14:09:27 +00:00
|
|
|
dump_file = util.realpath(DataStorage:getDataDir()) .. "/terminal_output.txt",
|
|
|
|
items_per_page = G_reader_settings:readSetting("items_per_page") or 16,
|
|
|
|
settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/terminal_shortcuts.lua"),
|
|
|
|
shortcuts_dialog = nil,
|
|
|
|
shortcuts_menu = nil,
|
|
|
|
-- shortcuts_file = DataStorage:getSettingsDir() .. "/terminal_shortcuts.lua",
|
|
|
|
shortcuts = {},
|
|
|
|
source = "terminal",
|
2017-04-02 06:50:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function Terminal:init()
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
2020-09-02 14:09:27 +00:00
|
|
|
self.shortcuts = self.settings:readSetting("shortcuts") or {}
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:saveShortcuts()
|
|
|
|
self.settings:saveSetting("shortcuts", self.shortcuts)
|
|
|
|
self.settings:flush()
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("Shortcuts saved"),
|
|
|
|
timeout = 2
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:manageShortcuts()
|
|
|
|
self.shortcuts_dialog = CenterContainer:new {
|
|
|
|
dimen = Screen:getSize(),
|
|
|
|
}
|
|
|
|
self.shortcuts_menu = Menu:new{
|
|
|
|
show_parent = self.ui,
|
|
|
|
width = Screen:getWidth(),
|
|
|
|
height = Screen:getHeight(),
|
|
|
|
covers_fullscreen = true, -- hint for UIManager:_repaint()
|
|
|
|
is_borderless = true,
|
|
|
|
is_popout = false,
|
|
|
|
perpage = self.items_per_page,
|
|
|
|
onMenuHold = self.onMenuHoldShortcuts,
|
|
|
|
_manager = self,
|
|
|
|
}
|
|
|
|
table.insert(self.shortcuts_dialog, self.shortcuts_menu)
|
|
|
|
self.shortcuts_menu.close_callback = function()
|
|
|
|
UIManager:close(self.shortcuts_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- sort the shortcuts:
|
|
|
|
if #self.shortcuts > 0 then
|
|
|
|
table.sort(self.shortcuts, function(v1, v2)
|
|
|
|
return v1.text < v2.text
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
self:updateItemTable()
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:updateItemTable()
|
|
|
|
local item_table = {}
|
|
|
|
if #self.shortcuts > 0 then
|
|
|
|
local actions_count = 3 -- separator + actions
|
|
|
|
for nr, f in ipairs(self.shortcuts) do
|
|
|
|
local item = {
|
|
|
|
nr = nr,
|
|
|
|
text = f.text,
|
|
|
|
commands = f.commands,
|
|
|
|
editable = true,
|
|
|
|
deletable = true,
|
|
|
|
callback = function()
|
|
|
|
-- so we know which middle button to display in the results:
|
|
|
|
self.source = "shortcut"
|
|
|
|
-- execute immediately, skip terminal dialog:
|
|
|
|
self.command = self:ensureWhitelineAfterCommands(f.commands)
|
|
|
|
Trapper:wrap(function()
|
|
|
|
self:execute()
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
table.insert(item_table, item)
|
|
|
|
-- add page actions at end of each page with shortcuts:
|
|
|
|
local factor = self.items_per_page - actions_count
|
|
|
|
if nr % factor == 0 or nr == #self.shortcuts then
|
|
|
|
-- insert "separator":
|
|
|
|
table.insert(item_table, {
|
|
|
|
text = " ",
|
|
|
|
deletable = false,
|
|
|
|
editable = false,
|
|
|
|
callback = function()
|
|
|
|
self:manageShortcuts()
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
-- actions:
|
|
|
|
self:insertPageActions(item_table)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- no shortcuts defined yet:
|
|
|
|
else
|
|
|
|
self:insertPageActions(item_table)
|
|
|
|
end
|
|
|
|
local title = #self.shortcuts == 1 and _("Terminal shortcut") or _("Terminal shortcuts")
|
|
|
|
self.shortcuts_menu:switchItemTable(tostring(#self.shortcuts) .. " " .. title, item_table)
|
|
|
|
UIManager:show(self.shortcuts_dialog)
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:insertPageActions(item_table)
|
|
|
|
table.insert(item_table, {
|
|
|
|
text = " " .. _("to terminal…"),
|
|
|
|
deletable = false,
|
|
|
|
editable = false,
|
|
|
|
callback = function()
|
|
|
|
self:terminal()
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
table.insert(item_table, {
|
|
|
|
text = " " .. _("close…"),
|
|
|
|
deletable = false,
|
|
|
|
editable = false,
|
|
|
|
callback = function()
|
|
|
|
return false
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:onMenuHoldShortcuts(item)
|
|
|
|
if item.deletable or item.editable then
|
|
|
|
local shortcut_shortcuts_dialog
|
|
|
|
shortcut_shortcuts_dialog = ButtonDialog:new{
|
|
|
|
buttons = {{
|
|
|
|
{
|
|
|
|
text = _("Edit name"),
|
|
|
|
enabled = item.editable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(shortcut_shortcuts_dialog)
|
|
|
|
if self._manager.shortcuts_dialog ~= nil then
|
|
|
|
UIManager:close(self._manager.shortcuts_dialog)
|
|
|
|
self._manager.shortcuts_dialog = nil
|
|
|
|
end
|
|
|
|
self._manager:editName(item)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Edit commands"),
|
|
|
|
enabled = item.editable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(shortcut_shortcuts_dialog)
|
|
|
|
if self._manager.shortcuts_dialog ~= nil then
|
|
|
|
UIManager:close(self._manager.shortcuts_dialog)
|
|
|
|
self._manager.shortcuts_dialog = nil
|
|
|
|
end
|
|
|
|
self._manager:editCommands(item)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Copy"),
|
|
|
|
enabled = item.editable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(shortcut_shortcuts_dialog)
|
|
|
|
if self._manager.shortcuts_dialog ~= nil then
|
|
|
|
UIManager:close(self._manager.shortcuts_dialog)
|
|
|
|
self._manager.shortcuts_dialog = nil
|
|
|
|
end
|
|
|
|
self._manager:copyCommands(item)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Delete"),
|
|
|
|
enabled = item.deletable,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(shortcut_shortcuts_dialog)
|
|
|
|
if self._manager.shortcuts_dialog ~= nil then
|
|
|
|
UIManager:close(self._manager.shortcuts_dialog)
|
|
|
|
self._manager.shortcuts_dialog = nil
|
|
|
|
end
|
|
|
|
self._manager:deleteShortcut(item)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
UIManager:show(shortcut_shortcuts_dialog)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:copyCommands(item)
|
|
|
|
local new_item = {
|
|
|
|
text = item.text .. " (copy)",
|
|
|
|
commands = item.commands
|
|
|
|
}
|
|
|
|
table.insert(self.shortcuts, new_item)
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("Shortcut copied"),
|
|
|
|
timeout = 2
|
|
|
|
})
|
|
|
|
self:saveShortcuts()
|
|
|
|
self:manageShortcuts()
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:editCommands(item)
|
|
|
|
local edit_dialog
|
|
|
|
edit_dialog = InputDialog:new{
|
|
|
|
title = T(_('Edit commands for "%1"'), item.text),
|
|
|
|
input = item.commands,
|
|
|
|
width = Screen:getWidth() * 0.9,
|
|
|
|
para_direction_rtl = false, -- force LTR
|
|
|
|
input_type = "string",
|
|
|
|
allow_newline = true,
|
|
|
|
cursor_at_end = true,
|
|
|
|
fullscreen = true,
|
|
|
|
buttons = {{{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(edit_dialog)
|
|
|
|
edit_dialog = nil
|
|
|
|
self:manageShortcuts()
|
|
|
|
end,
|
|
|
|
}, {
|
|
|
|
text = _("Save"),
|
|
|
|
callback = function()
|
|
|
|
local input = edit_dialog:getInputText()
|
|
|
|
UIManager:close(edit_dialog)
|
|
|
|
edit_dialog = nil
|
|
|
|
if input:match("[A-Za-z]") then
|
|
|
|
self.shortcuts[item.nr]["commands"] = input
|
|
|
|
self:saveShortcuts()
|
|
|
|
self:manageShortcuts()
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}}},
|
|
|
|
}
|
|
|
|
UIManager:show(edit_dialog)
|
|
|
|
edit_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:editName(item)
|
|
|
|
local edit_dialog
|
|
|
|
edit_dialog = InputDialog:new{
|
|
|
|
title = _("Edit name"),
|
|
|
|
input = item.text,
|
|
|
|
width = Screen:getWidth() * 0.9,
|
|
|
|
para_direction_rtl = false, -- force LTR
|
|
|
|
input_type = "string",
|
|
|
|
allow_newline = false,
|
|
|
|
cursor_at_end = true,
|
|
|
|
fullscreen = true,
|
|
|
|
buttons = {{{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(edit_dialog)
|
|
|
|
edit_dialog = nil
|
|
|
|
self:manageShortcuts()
|
|
|
|
end,
|
|
|
|
}, {
|
|
|
|
text = _("Save"),
|
|
|
|
callback = function()
|
|
|
|
local input = edit_dialog:getInputText()
|
|
|
|
UIManager:close(edit_dialog)
|
|
|
|
edit_dialog = nil
|
|
|
|
if input:match("[A-Za-z]") then
|
|
|
|
self.shortcuts[item.nr]["text"] = input
|
|
|
|
self:saveShortcuts()
|
|
|
|
self:manageShortcuts()
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}}},
|
|
|
|
}
|
|
|
|
UIManager:show(edit_dialog)
|
|
|
|
edit_dialog:onShowKeyboard()
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:deleteShortcut(item)
|
|
|
|
local shortcuts = {}
|
|
|
|
for _, element in ipairs(self.shortcuts) do
|
|
|
|
if element.text ~= item.text and element.commands ~= item.commands then
|
|
|
|
table.insert(shortcuts, element)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.shortcuts = shortcuts
|
|
|
|
self:saveShortcuts()
|
|
|
|
self:manageShortcuts()
|
2017-04-02 06:50:24 +00:00
|
|
|
end
|
|
|
|
|
2020-08-30 10:15:02 +00:00
|
|
|
function Terminal:onTerminalStart()
|
2020-09-02 14:09:27 +00:00
|
|
|
-- if shortcut commands are defined, go directly to the the shortcuts manager (so we can execute scripts more quickly):
|
|
|
|
if #self.shortcuts == 0 then
|
|
|
|
self:terminal()
|
|
|
|
else
|
|
|
|
self:manageShortcuts()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:terminal()
|
2017-04-02 06:50:24 +00:00
|
|
|
self.input = InputDialog:new{
|
2020-09-02 14:09:27 +00:00
|
|
|
title = _("Enter a command and press \"Execute\""),
|
|
|
|
input = self.command:gsub("\n+$", ""),
|
2019-12-06 21:55:35 +00:00
|
|
|
para_direction_rtl = false, -- force LTR
|
2017-04-02 06:50:24 +00:00
|
|
|
input_type = "string",
|
2020-08-30 10:15:02 +00:00
|
|
|
allow_newline = true,
|
|
|
|
cursor_at_end = true,
|
2020-09-02 14:09:27 +00:00
|
|
|
fullscreen = true,
|
2017-04-02 06:50:24 +00:00
|
|
|
buttons = {{{
|
2020-09-02 14:09:27 +00:00
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.input)
|
|
|
|
end,
|
|
|
|
}, {
|
|
|
|
text = _("Shortcuts"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.input)
|
|
|
|
self:manageShortcuts()
|
|
|
|
end,
|
|
|
|
}, {
|
|
|
|
text = _("Save"),
|
|
|
|
callback = function()
|
|
|
|
local input = self.input:getInputText()
|
|
|
|
if input:match("[A-Za-z]") then
|
|
|
|
|
|
|
|
local function callback(name)
|
|
|
|
local new_shortcut = {
|
|
|
|
text = name,
|
|
|
|
commands = input,
|
|
|
|
}
|
|
|
|
table.insert(self.shortcuts, new_shortcut)
|
|
|
|
self:saveShortcuts()
|
|
|
|
end
|
|
|
|
|
|
|
|
local prompt
|
|
|
|
prompt = InputDialog:new{
|
|
|
|
title = _("Name"),
|
|
|
|
input = "",
|
|
|
|
input_type = "text",
|
|
|
|
fullscreen = true,
|
|
|
|
condensed = true,
|
|
|
|
allow_newline = false,
|
|
|
|
cursor_at_end = true,
|
|
|
|
buttons = {{{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(prompt)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Save"),
|
|
|
|
is_enter_default = true,
|
|
|
|
callback = function()
|
|
|
|
local newval = prompt:getInputText()
|
|
|
|
UIManager:close(prompt)
|
|
|
|
callback(newval)
|
|
|
|
end,
|
|
|
|
}}}
|
|
|
|
}
|
|
|
|
UIManager:show(prompt)
|
|
|
|
prompt:onShowKeyboard()
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}, {
|
|
|
|
text = _("Execute"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(self.input)
|
|
|
|
-- so we know which middle button to display in the results:
|
|
|
|
self.source = "terminal"
|
|
|
|
self.command = self:ensureWhitelineAfterCommands(self.input:getInputText())
|
|
|
|
Trapper:wrap(function()
|
|
|
|
self:execute()
|
|
|
|
end)
|
|
|
|
end,
|
|
|
|
}}},
|
2017-04-02 06:50:24 +00:00
|
|
|
}
|
|
|
|
UIManager:show(self.input)
|
2018-03-30 10:46:36 +00:00
|
|
|
self.input:onShowKeyboard()
|
2017-04-02 06:50:24 +00:00
|
|
|
end
|
|
|
|
|
2020-09-02 14:09:27 +00:00
|
|
|
-- for prettier formatting of output by separating commands and result thereof with a whiteline:
|
|
|
|
function Terminal:ensureWhitelineAfterCommands(commands)
|
|
|
|
if string.sub(commands, -1) ~= "\n" then
|
|
|
|
commands = commands .. "\n"
|
|
|
|
end
|
|
|
|
return commands
|
|
|
|
end
|
|
|
|
|
2017-04-02 06:50:24 +00:00
|
|
|
function Terminal:execute()
|
2020-03-20 22:41:36 +00:00
|
|
|
local wait_msg = InfoMessage:new{
|
2017-04-03 08:16:58 +00:00
|
|
|
text = _("Executing…"),
|
2020-03-20 22:41:36 +00:00
|
|
|
}
|
|
|
|
UIManager:show(wait_msg)
|
2017-04-14 19:12:28 +00:00
|
|
|
local entries = { self.command }
|
2020-03-20 22:41:36 +00:00
|
|
|
local command = self.command .. " 2>&1 ; echo" -- ensure we get stderr and output something
|
|
|
|
local completed, result_str = Trapper:dismissablePopen(command, wait_msg)
|
|
|
|
if completed then
|
|
|
|
table.insert(entries, result_str)
|
|
|
|
self:dump(entries)
|
|
|
|
table.insert(entries, _("Output was also written to"))
|
|
|
|
table.insert(entries, self.dump_file)
|
2017-04-02 06:50:24 +00:00
|
|
|
else
|
2020-03-20 22:41:36 +00:00
|
|
|
table.insert(entries, _("Execution canceled."))
|
2017-04-02 06:50:24 +00:00
|
|
|
end
|
2020-03-20 22:41:36 +00:00
|
|
|
UIManager:close(wait_msg)
|
2020-08-30 10:15:02 +00:00
|
|
|
local viewer
|
2020-09-02 14:09:27 +00:00
|
|
|
local buttons_table
|
|
|
|
local back_button = {
|
|
|
|
text = _("Back"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(viewer)
|
|
|
|
if self.source == "terminal" then
|
|
|
|
self:terminal()
|
|
|
|
else
|
|
|
|
self:manageShortcuts()
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
local close_button = {
|
|
|
|
text = _("Close"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(viewer)
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
if self.source == "terminal" then
|
2020-08-30 10:15:02 +00:00
|
|
|
buttons_table = {
|
|
|
|
{
|
2020-09-02 14:09:27 +00:00
|
|
|
back_button,
|
2020-08-30 10:15:02 +00:00
|
|
|
{
|
2020-09-02 14:09:27 +00:00
|
|
|
text = _("Shortcuts"),
|
|
|
|
-- switch to shortcuts:
|
2020-08-30 10:15:02 +00:00
|
|
|
callback = function()
|
|
|
|
UIManager:close(viewer)
|
2020-09-02 14:09:27 +00:00
|
|
|
self:manageShortcuts()
|
2020-08-30 10:15:02 +00:00
|
|
|
end,
|
|
|
|
},
|
2020-09-02 14:09:27 +00:00
|
|
|
close_button,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
else
|
|
|
|
buttons_table = {
|
|
|
|
{
|
|
|
|
back_button,
|
2020-08-30 10:15:02 +00:00
|
|
|
{
|
2020-09-02 14:09:27 +00:00
|
|
|
text = _("Terminal"),
|
|
|
|
-- switch to terminal:
|
2020-08-30 10:15:02 +00:00
|
|
|
callback = function()
|
|
|
|
UIManager:close(viewer)
|
2020-09-02 14:09:27 +00:00
|
|
|
self:terminal()
|
2020-08-30 10:15:02 +00:00
|
|
|
end,
|
|
|
|
},
|
2020-09-02 14:09:27 +00:00
|
|
|
close_button,
|
2020-08-30 10:15:02 +00:00
|
|
|
},
|
2020-09-02 14:09:27 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
viewer = TextViewer:new{
|
|
|
|
title = _("Command output"),
|
|
|
|
text = table.concat(entries, "\n"),
|
|
|
|
justified = false,
|
|
|
|
text_face = Font:getFace("smallinfont"),
|
|
|
|
buttons_table = buttons_table,
|
2020-08-30 10:15:02 +00:00
|
|
|
}
|
|
|
|
UIManager:show(viewer)
|
2017-04-02 06:50:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:dump(entries)
|
|
|
|
local content = table.concat(entries, "\n")
|
|
|
|
local file = io.open(self.dump_file, "w")
|
|
|
|
if file then
|
|
|
|
file:write(content)
|
|
|
|
file:close()
|
|
|
|
else
|
|
|
|
logger.warn("Failed to dump terminal output " .. content .. " to " .. self.dump_file)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Terminal:addToMainMenu(menu_items)
|
|
|
|
menu_items.terminal = {
|
|
|
|
text = _("Terminal emulator"),
|
2018-09-04 21:55:58 +00:00
|
|
|
keep_menu_open = true,
|
2017-04-02 06:50:24 +00:00
|
|
|
callback = function()
|
2020-08-30 10:15:02 +00:00
|
|
|
self:onTerminalStart()
|
2017-04-02 06:50:24 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
return Terminal
|