diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index d5d7fe0b4..23f2c0355 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -1,4 +1,5 @@ local BD = require("ui/bidi") +local BlitBuffer = require("ffi/blitbuffer") local ButtonDialog = require("ui/widget/buttondialog") local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") @@ -18,10 +19,22 @@ local ffiUtil = require("ffi/util") local time = require("ui/time") local _ = require("gettext") local C_ = _.pgettext +local N_ = _.ngettext local T = ffiUtil.template local Screen = Device.screen -local ReaderHighlight = InputContainer:extend{} +local ReaderHighlight = InputContainer:extend{ + highlight_colors = { + {_("Red"), "red"}, + {_("Orange"), "orange"}, + {_("Yellow"), "yellow"}, + {_("Green"), "green"}, + {_("Cyan"), "cyan"}, + {_("Blue"), "blue"}, + {_("Purple"), "purple"}, + {_("Gray"), "gray"}, + } +} local function inside_box(pos, box) if pos then @@ -54,6 +67,7 @@ function ReaderHighlight:init() self._current_indicator_pos = nil self._previous_indicator_pos = nil self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = time:now()} + self._fallback_color = Screen:isColorEnabled() and "yellow" or "gray" self:registerKeyEvents() @@ -405,7 +419,55 @@ function ReaderHighlight:addToMainMenu(menu_items) end table.insert(menu_items.highlight_options.sub_item_table, { text_func = function() - return T(_("Highlight opacity: %1"), G_reader_settings:readSetting("highlight_lighten_factor", 0.2)) + local saved_color = self.view.highlight.saved_color or self._fallback_color + for __, v in ipairs(self.highlight_colors) do + if v[2] == saved_color then + return T(_("Highlight color: %1"), string.lower(v[1])) + end + end + return T(_("Highlight color: %1"), saved_color) + end, + enabled_func = function() + return self.view.highlight.saved_drawer ~= "invert" + end, + callback = function(touchmenu_instance) + local default_color = G_reader_settings:readSetting("highlight_color", self._fallback_color) + local saved_color = self.view.highlight.saved_color or self._fallback_color + local radio_buttons = {} + for _, v in ipairs(self.highlight_colors) do + table.insert(radio_buttons, { + { + text = v[1], + checked = v[2] == saved_color, + bgcolor = BlitBuffer.colorFromName(v[2]) or BlitBuffer.Color8(bit.bxor(0xFF * self.view.highlight.lighten_factor, 0xFF)), + provider = v[2], + }, + }) + end + UIManager:show(require("ui/widget/radiobuttonwidget"):new{ + title_text = _("Highlight color"), + width_factor = 0.5, + keep_shown_on_apply = false, + radio_buttons = radio_buttons, + default_provider = default_color, + callback = function(radio) + self.view.highlight.saved_color = radio.provider + UIManager:setDirty(self.dialog, "ui") + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + end, + hold_callback = function(touchmenu_instance) + G_reader_settings:saveSetting("highlight_color", self.view.highlight.saved_color) + UIManager:show(Notification:new{ + text = T(_("Default highlight color changed to '%1'."), self.view.highlight.saved_color), + }) + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + table.insert(menu_items.highlight_options.sub_item_table, { + text_func = function() + return T(_("Gray highlight opacity: %1"), G_reader_settings:readSetting("highlight_lighten_factor", 0.2)) end, enabled_func = function() return self.view.highlight.saved_drawer == "lighten" @@ -422,8 +484,8 @@ function ReaderHighlight:addToMainMenu(menu_items) value_hold_step = 0.25, default_value = 0.2, keep_shown_on_apply = true, - title_text = _("Highlight opacity"), - info_text = _("The higher the value, the darker the highlight."), + title_text = _("Gray highlight opacity"), + info_text = _("The higher the value, the darker the gray."), callback = function(spin) G_reader_settings:saveSetting("highlight_lighten_factor", spin.value) self.view.highlight.lighten_factor = spin.value @@ -475,6 +537,35 @@ function ReaderHighlight:addToMainMenu(menu_items) }) end, }) + table.insert(menu_items.highlight_options.sub_item_table, { + text = _("Apply default style to all highlights"), + callback = function(touchmenu_instance) + UIManager:show(ConfirmBox:new{ + text = _("Are you sure you want to edit all highlights."), + icon = "texture-box", + ok_callback = function() + local count = 0 + for _, items in pairs(self.view.highlight.saved) do + if items then + count = count + #items + for i = 1, #items do + local item = items[i] + item.drawer = self.view.highlight.saved_drawer + item.color = self.view.highlight.saved_color + end + end + end + if count > 0 then + UIManager:setDirty(self.dialog, "ui") + UIManager:show(Notification:new{ + text = T(N_("Applied default style to 1 highlight", + "Applied default style to %1 highlights", count), count), + }) + end + end + }) + end, + }) if self.document.info.has_pages then menu_items.panel_zoom_options = { text = _("Panel zoom (manga/comic)"), @@ -1012,7 +1103,7 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text) self.edit_highlight_dialog = nil end, }, - } + }, } if self.ui.rolling then @@ -1728,7 +1819,6 @@ function ReaderHighlight:onCycleHighlightStyle() next_style_num = 1 end self.view.highlight.saved_drawer = highlight_style[next_style_num][2] - self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer) UIManager:show(Notification:new{ text = T(_("Default highlight style changed to '%1'."), highlight_style[next_style_num][1]), }) @@ -1867,6 +1957,7 @@ function ReaderHighlight:saveHighlight(extend_to_sentence) pboxes = self.selected_text.pboxes, ext = self.selected_text.ext, drawer = self.view.highlight.saved_drawer, + color = self.view.highlight.saved_color, chapter = chapter_name, } table.insert(self.view.highlight.saved[page], hl_item) @@ -2017,10 +2108,32 @@ function ReaderHighlight:editHighlightStyle(page, i) datetime = item.datetime, }))) end - self:showHighlightStyleDialog(apply_drawer, item.drawer) + self:showHighlightStyleDialog(apply_drawer, item.drawer, page, i) +end + +function ReaderHighlight:editHighlightColor(page, i) + local item = self.view.highlight.saved[page][i] + local apply_color = function(color) + self:writePdfAnnotation("delete", page, item) + item.color = color + if self.ui.paging then + self:writePdfAnnotation("save", page, item) + local bm_note = self.ui.bookmark:getBookmarkNote(item) + if bm_note then + self:writePdfAnnotation("content", page, item, bm_note) + end + end + UIManager:setDirty(self.dialog, "ui") + self.ui:handleEvent(Event:new("BookmarkUpdated", + self.ui.bookmark:getBookmarkForHighlight({ + page = self.ui.paging and page or item.pos0, + datetime = item.datetime, + }))) + end + self:showHighlightColorDialog(apply_color, item.color) end -function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer) +function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer, page, i) local default_drawer, keep_shown_on_apply if item_drawer then -- called from editHighlightStyle default_drawer = self.view.highlight.saved_drawer or @@ -2038,7 +2151,7 @@ function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer) }) end local RadioButtonWidget = require("ui/widget/radiobuttonwidget") - UIManager:show(RadioButtonWidget:new{ + local ctor = { title_text = _("Highlight style"), width_factor = 0.5, keep_shown_on_apply = keep_shown_on_apply, @@ -2047,6 +2160,52 @@ function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer) callback = function(radio) caller_callback(radio.provider) end, + } + if page and i then + -- called from editHighlightStyle + ctor.extra_text = _("Highlight color") + ctor.extra_callback = function(this) + local item = self.view.highlight.saved[page][i] + if item.drawer == "invert" then + UIManager:show(InfoMessage:new{ text = _("Colors unavailable when highlight style is set to 'Invert'") }) + else + -- Close the style dialog before showing the color dialog + this:onClose() + self:editHighlightColor(page, i) + end + end + end + UIManager:show(RadioButtonWidget:new(ctor)) +end + +function ReaderHighlight:showHighlightColorDialog(caller_callback, item_color) + local default_color, keep_shown_on_apply + if item_color then -- called from editHighlightColor + default_color = self.view.highlight.saved_color or + G_reader_settings:readSetting("highlight_color", self._fallback_color) + keep_shown_on_apply = true + end + local radio_buttons = {} + for _, v in ipairs(self.highlight_colors) do + table.insert(radio_buttons, { + { + text = v[1], + checked = item_color == v[2], + bgcolor = BlitBuffer.colorFromName(v[2]) or BlitBuffer.Color8(bit.bxor(0xFF * self.view.highlight.lighten_factor, 0xFF)), + provider = v[2], + }, + }) + end + local RadioButtonWidget = require("ui/widget/radiobuttonwidget") + UIManager:show(RadioButtonWidget:new{ + title_text = _("Highlight color"), + width_factor = 0.5, + keep_shown_on_apply = keep_shown_on_apply, + radio_buttons = radio_buttons, + default_provider = default_color, + callback = function(radio) + caller_callback(radio.provider) + end, }) end @@ -2175,7 +2334,8 @@ function ReaderHighlight:getSavedExtendedHighlightPage(hl_or_bm, page, index) end local item = {} item.datetime = highlight.datetime - item.drawer = highlight.drawer + item.drawer = highlight.drawer or self.highlight.saved_drawer + item.color = highlight.color or self.highlight.saved_color item.pos0 = highlight.ext[page].pos0 item.pos0.zoom = highlight.pos0.zoom item.pos0.rotation = highlight.pos0.rotation @@ -2190,6 +2350,8 @@ end function ReaderHighlight:onReadSettings(config) self.view.highlight.saved_drawer = config:readSetting("highlight_drawer") or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer + self.view.highlight.saved_color = config:readSetting("highlight_color") + or G_reader_settings:readSetting("highlight_color") or self.view.highlight.saved_color self.view.highlight.disabled = G_reader_settings:has("default_highlight_action") and G_reader_settings:readSetting("default_highlight_action") == "nothing" @@ -2219,6 +2381,7 @@ end function ReaderHighlight:onSaveSettings() self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer) + self.ui.doc_settings:saveSetting("highlight_color", self.view.highlight.saved_color) self.ui.doc_settings:saveSetting("panel_zoom_enabled", self.panel_zoom_enabled) end diff --git a/frontend/apps/reader/modules/readerview.lua b/frontend/apps/reader/modules/readerview.lua index 7c8279434..838610d07 100644 --- a/frontend/apps/reader/modules/readerview.lua +++ b/frontend/apps/reader/modules/readerview.lua @@ -95,6 +95,7 @@ function ReaderView:init() temp_drawer = "invert", temp = {}, saved_drawer = "lighten", + saved_color = Screen:isColorEnabled() and "yellow" or "gray", saved = {}, indicator = nil, -- geom: non-touch highlight position indicator: {x = 50, y=50} } @@ -213,7 +214,8 @@ function ReaderView:paintTo(bb, x, y) -- dim last read area if not self.dim_area:isEmpty() and self:isOverlapAllowed() then if self.page_overlap_style == "dim" then - bb:dimRect( + -- NOTE: "dim", as in make black text fainter, e.g., lighten the rect + bb:lightenRect( self.dim_area.x, self.dim_area.y, self.dim_area.w, self.dim_area.h ) @@ -561,13 +563,13 @@ function ReaderView:drawPageSavedHighlight(bb, x, y) for _, item in ipairs(items) do local boxes = self.document:getPageBoxesFromPositions(page, item.pos0, item.pos1) if boxes then - local drawer = item.drawer or self.highlight.saved_drawer + local color = Blitbuffer.colorFromName(item.color) local draw_note_mark = self.highlight.note_mark and self.ui.bookmark:getBookmarkNote({datetime = item.datetime}) for _, box in ipairs(boxes) do local rect = self:pageToScreenTransform(page, box) if rect then - self:drawHighlightRect(bb, x, y, rect, drawer, draw_note_mark) + self:drawHighlightRect(bb, x, y, rect, item.drawer, color, draw_note_mark) if draw_note_mark and self.highlight.note_mark == "sidemark" then draw_note_mark = false -- side mark in the first line only end @@ -609,12 +611,12 @@ function ReaderView:drawXPointerSavedHighlight(bb, x, y) if start_pos <= cur_view_bottom and end_pos >= cur_view_top then local boxes = self.document:getScreenBoxesFromPositions(pos0, pos1, true) -- get_segments=true if boxes then - local drawer = item.drawer or self.highlight.saved_drawer + local color = Blitbuffer.colorFromName(item.color) local draw_note_mark = self.highlight.note_mark and self.ui.bookmark:getBookmarkNote({datetime = item.datetime}) for _, box in ipairs(boxes) do if box.h ~= 0 then - self:drawHighlightRect(bb, x, y, box, drawer, draw_note_mark) + self:drawHighlightRect(bb, x, y, box, item.drawer, color, draw_note_mark) if draw_note_mark and self.highlight.note_mark == "sidemark" then draw_note_mark = false -- side mark in the first line only end @@ -627,24 +629,58 @@ function ReaderView:drawXPointerSavedHighlight(bb, x, y) end -- end for all saved highlight end -function ReaderView:drawHighlightRect(bb, _x, _y, rect, drawer, draw_note_mark) +function ReaderView:drawHighlightRect(bb, _x, _y, rect, drawer, color, draw_note_mark) local x, y, w, h = rect.x, rect.y, rect.w, rect.h if drawer == "lighten" then - bb:lightenRect(x, y, w, h, self.highlight.lighten_factor) + if not color then + bb:darkenRect(x, y, w, h, self.highlight.lighten_factor) + else + if bb:getInverse() == 1 then + -- MUL doesn't really work on a black background, so, switch to OVER if we're in software nightmode... + -- NOTE: If we do *not* invert the color here, it *will* get inverted by the blitter given that the target bb is inverted. + -- While not particularly pretty, this (roughly) matches with hardware nightmode, *and* how MuPDF renders highlights... + -- But it's *really* not pretty (https://github.com/koreader/koreader/pull/11044#issuecomment-1902886069), so we'll fix it ;p. + local c = Blitbuffer.ColorRGB32(color.r, color.g, color.b, 0xFF * self.highlight.lighten_factor):invert() + bb:blendRectRGB32(x, y, w, h, c) + else + bb:multiplyRectRGB(x, y, w, h, color) + end + end elseif drawer == "underscore" then - bb:paintRect(x, y + h - 1, w, Size.line.medium, Blitbuffer.COLOR_GRAY) + if not color then + color = Blitbuffer.COLOR_GRAY + end + if Blitbuffer.isColor8(color) then + bb:paintRect(x, y + h - 1, w, Size.line.medium, color) + else + bb:paintRectRGB32(x, y + h - 1, w, Size.line.medium, color) + end elseif drawer == "strikeout" then + if not color then + color = Blitbuffer.COLOR_BLACK + end local line_y = y + math.floor(h / 2) + 1 if self.ui.paging then line_y = line_y + 2 end - bb:paintRect(x, line_y, w, Size.line.medium, Blitbuffer.COLOR_BLACK) + if Blitbuffer.isColor8(color) then + bb:paintRect(x, line_y, w, Size.line.medium, color) + else + bb:paintRectRGB32(x, line_y, w, Size.line.medium, color) + end elseif drawer == "invert" then bb:invertRect(x, y, w, h) end if draw_note_mark then + if not color then + color = Blitbuffer.COLOR_BLACK + end if self.highlight.note_mark == "underline" then - bb:paintRect(x, y + h - 1, w, Size.line.medium, Blitbuffer.COLOR_BLACK) + if Blitbuffer.isColor8(color) then + bb:paintRect(x, y + h - 1, w, Size.line.medium, color) + else + bb:paintRectRGB32(x, y + h - 1, w, Size.line.medium, color) + end else local note_mark_pos_x if self.ui.paging or @@ -655,7 +691,11 @@ function ReaderView:drawHighlightRect(bb, _x, _y, rect, drawer, draw_note_mark) note_mark_pos_x = self.note_mark_pos_x2 end if self.highlight.note_mark == "sideline" then - bb:paintRect(note_mark_pos_x, y, self.note_mark_line_w, h, Blitbuffer.COLOR_BLACK) + if Blitbuffer.isColor8(color) then + bb:paintRect(note_mark_pos_x, y, self.note_mark_line_w, h, color) + else + bb:paintRectRGB32(note_mark_pos_x, y, self.note_mark_line_w, h, color) + end elseif self.highlight.note_mark == "sidemark" then self.note_mark_sign:paintTo(bb, note_mark_pos_x, y) end diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index aba0ad3a6..9b246c4a3 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -1,3 +1,4 @@ +local BlitBuffer = require("ffi/blitbuffer") local CacheItem = require("cacheitem") local CanvasContext = require("document/canvascontext") local DocCache = require("document/doccache") @@ -249,6 +250,7 @@ function PdfDocument:saveHighlight(pageno, item) local quadpoints, n = _quadpointsFromPboxes(item.pboxes) local page = self._document:openPage(pageno) local annot_type = C.PDF_ANNOT_HIGHLIGHT + local annot_color = BlitBuffer.colorFromName(item.color) if item.drawer == "lighten" then annot_type = C.PDF_ANNOT_HIGHLIGHT elseif item.drawer == "underscore" then @@ -256,7 +258,9 @@ function PdfDocument:saveHighlight(pageno, item) elseif item.drawer == "strikeout" then annot_type = C.PDF_ANNOT_STRIKE_OUT end - page:addMarkupAnnotation(quadpoints, n, annot_type) -- may update/adjust quadpoints + -- NOTE: For highlights, display style may differ compared to ReaderView:drawHighlightRect... + -- (e.g., we do a MUL blend, MuPDF currently appears to do an OVER blend). + page:addMarkupAnnotation(quadpoints, n, annot_type, annot_color) -- may update/adjust quadpoints -- Update pboxes with the possibly adjusted coordinates (this will have it updated -- in self.view.highlight.saved[page]) item.pboxes = _quadpointsToPboxes(quadpoints, n) diff --git a/frontend/ui/widget/checkbutton.lua b/frontend/ui/widget/checkbutton.lua index ef7e785aa..71168d36e 100644 --- a/frontend/ui/widget/checkbutton.lua +++ b/frontend/ui/widget/checkbutton.lua @@ -67,11 +67,14 @@ function CheckButton:initCheckButton(checked) show_parent = self.show_parent or self, } end + local fgcolor = self.fgcolor or Blitbuffer.COLOR_BLACK self._textwidget = TextBoxWidget:new{ text = self.text, face = self.face, width = (self.width or self.parent:getAddedWidgetAvailableWidth()) - self._checkmark.dimen.w, - fgcolor = self.enabled and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_DARK_GRAY, + bold = self.bold, + fgcolor = self.enabled and fgcolor or Blitbuffer.COLOR_DARK_GRAY, + bgcolor = self.bgcolor, } local textbox_shift = math.max(0, self._checkmark.baseline - self._textwidget:getBaseline()) self._verticalgroup = VerticalGroup:new{ diff --git a/frontend/ui/widget/container/framecontainer.lua b/frontend/ui/widget/container/framecontainer.lua index 855684f6f..51d16cf55 100644 --- a/frontend/ui/widget/container/framecontainer.lua +++ b/frontend/ui/widget/container/framecontainer.lua @@ -160,7 +160,7 @@ function FrameContainer:paintTo(bb, x, y) container_height - 2*self.bordersize) end if self.dim then - bb:dimRect(x + self.bordersize, y + self.bordersize, + bb:lightenRect(x + self.bordersize, y + self.bordersize, container_width - 2*self.bordersize, container_height - 2*self.bordersize) end diff --git a/frontend/ui/widget/imagewidget.lua b/frontend/ui/widget/imagewidget.lua index de5d4aa24..c0192d2d2 100644 --- a/frontend/ui/widget/imagewidget.lua +++ b/frontend/ui/widget/imagewidget.lua @@ -574,7 +574,7 @@ function ImageWidget:paintTo(bb, x, y) --- This would require the *original* transparent icon, not the flattened one in the cache. --- c.f., https://github.com/koreader/koreader/pull/6937#issuecomment-748372429 for a PoC if self.dim then - bb:dimRect(x, y, size.w, size.h) + bb:lightenRect(x, y, size.w, size.h) end -- In night mode, invert all rendered images, so the original is -- displayed when the whole screen is inverted by night mode. diff --git a/frontend/ui/widget/radiobuttontable.lua b/frontend/ui/widget/radiobuttontable.lua index c8ac9c138..635f262da 100644 --- a/frontend/ui/widget/radiobuttontable.lua +++ b/frontend/ui/widget/radiobuttontable.lua @@ -66,6 +66,10 @@ function RadioButtonTable:init() radio = true, provider = btn_entry.provider, + bold = btn_entry.bold, + fgcolor = btn_entry.fgcolor, + bgcolor = btn_entry.bgcolor, + width = (self.width - sizer_space) / column_cnt, bordersize = 0, margin = 0, diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 9f5ac062e..d0dd35613 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -835,11 +835,18 @@ function TextBoxWidget:_renderText(start_row_idx, end_row_idx) h = h + self.line_glyph_extra_height if self._bb then self._bb:free() end local bbtype = nil - if self.line_num_to_image and self.line_num_to_image[start_row_idx] then + local color_fg = not Blitbuffer.isColor8(self.fgcolor) + local color_bg = not Blitbuffer.isColor8(self.bgcolor) + -- Color, whether from images or fg or bg, means we'll need an RGB32 buffer (if it makes sense, e.g., on a color screen). + if (self.line_num_to_image and self.line_num_to_image[start_row_idx]) or color_fg or color_bg then bbtype = Screen:isColorEnabled() and Blitbuffer.TYPE_BBRGB32 or Blitbuffer.TYPE_BB8 end self._bb = Blitbuffer.new(self.width, h, bbtype) - self._bb:fill(self.bgcolor) + if not color_bg then + self._bb:fill(self.bgcolor) + else + self._bb:paintRectRGB32(0, 0, self._bb:getWidth(), self._bb:getHeight(), self.bgcolor) + end local y = self.line_glyph_baseline if self.use_xtext then @@ -874,10 +881,17 @@ function TextBoxWidget:_renderText(start_row_idx, end_row_idx) if self._alt_color_for_rtl then color = xglyph.is_rtl and Blitbuffer.COLOR_DARK_GRAY or Blitbuffer.COLOR_BLACK end - self._bb:colorblitFrom(glyph.bb, - xglyph.x0 + glyph.l + xglyph.x_offset, - y - glyph.t - xglyph.y_offset, - 0, 0, glyph.bb:getWidth(), glyph.bb:getHeight(), color) + if not color_fg then + self._bb:colorblitFrom(glyph.bb, + xglyph.x0 + glyph.l + xglyph.x_offset, + y - glyph.t - xglyph.y_offset, + 0, 0, glyph.bb:getWidth(), glyph.bb:getHeight(), color) + else + self._bb:colorblitFromRGB32(glyph.bb, + xglyph.x0 + glyph.l + xglyph.x_offset, + y - glyph.t - xglyph.y_offset, + 0, 0, glyph.bb:getWidth(), glyph.bb:getHeight(), color) + end end end end diff --git a/plugins/terminal.koplugin/main.lua b/plugins/terminal.koplugin/main.lua index a94d9bc42..c71be8f31 100644 --- a/plugins/terminal.koplugin/main.lua +++ b/plugins/terminal.koplugin/main.lua @@ -58,7 +58,7 @@ end -- So sorry for the Tolinos with (Android 4.4.x). -- Maybe https://f-droid.org/de/packages/jackpal.androidterm/ could be an alternative then. if (Device:isAndroid() and Device.firmware_rev < 21) or not check_prerequisites() then - logger.warn("Terminal: Device doesn't meet some of the plugin's prerequisites") + logger.warn("Terminal: Device doesn't meet some of the plugin's requirements") return { disabled = true, } end