diff --git a/Makefile b/Makefile index 535a4ab03..58b044ab0 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ DEBIAN_DIR=$(PLATFORM_DIR)/debian KINDLE_DIR=$(PLATFORM_DIR)/kindle KOBO_DIR=$(PLATFORM_DIR)/kobo POCKETBOOK_DIR=$(PLATFORM_DIR)/pocketbook +REMARKABLE_DIR=$(PLATFORM_DIR)/remarkable SONY_PRSTUX_DIR=$(PLATFORM_DIR)/sony-prstux UBUNTUTOUCH_DIR=$(PLATFORM_DIR)/ubuntu-touch UBUNTUTOUCH_SDL_DIR:=$(UBUNTUTOUCH_DIR)/ubuntu-touch-sdl @@ -385,6 +386,35 @@ debianupdate: all rm -rf resources/fonts resources/icons/src && \ rm -rf ev_replay.py +REMARKABLE_PACKAGE:=koreader-remarkable$(KODEDUG_SUFFIX)-$(VERSION).zip +REMARKABLE_PACKAGE_OTA:=koreader-remarkable$(KODEDUG_SUFFIX)-$(VERSION).targz +remarkableupdate: all + # ensure that the binaries were built for ARM + file $(INSTALL_DIR)/koreader/luajit | grep ARM || exit 1 + # remove old package if any + rm -f $(REMARKABLE_PACKAGE) + # Remarkable scripts + cp $(REMARKABLE_DIR)/* $(INSTALL_DIR)/koreader + cp $(COMMON_DIR)/spinning_zsync $(INSTALL_DIR)/koreader + # create new package + cd $(INSTALL_DIR) && \ + zip -9 -r \ + ../$(REMARKABLE_PACKAGE) \ + koreader -x "koreader/resources/fonts/*" \ + "koreader/resources/icons/src/*" "koreader/spec/*" \ + $(ZIP_EXCLUDE) + # generate update package index file + zipinfo -1 $(REMARKABLE_PACKAGE) > \ + $(INSTALL_DIR)/koreader/ota/package.index + echo "koreader/ota/package.index" >> $(INSTALL_DIR)/koreader/ota/package.index + # update index file in zip package + cd $(INSTALL_DIR) && zip -u ../$(REMARKABLE_PACKAGE) \ + koreader/ota/package.index + # make gzip remarkable update for zsync OTA update + cd $(INSTALL_DIR) && \ + tar -I"gzip --rsyncable" -cah --no-recursion -f ../$(REMARKABLE_PACKAGE_OTA) \ + -T koreader/ota/package.index + SONY_PRSTUX_PACKAGE:=koreader-sony-prstux$(KODEDUG_SUFFIX)-$(VERSION).zip SONY_PRSTUX_PACKAGE_OTA:=koreader-sony-prstux$(KODEDUG_SUFFIX)-$(VERSION).targz sony-prstuxupdate: all @@ -461,6 +491,8 @@ else ifeq ($(TARGET), pocketbook) make pbupdate else ifeq ($(TARGET), sony-prstux) make sony-prstuxupdate +else ifeq ($(TARGET), remarkable) + make remarkableupdate else ifeq ($(TARGET), ubuntu-touch) make utupdate else ifeq ($(TARGET), debian) diff --git a/frontend/apps/filemanager/filemanagerutil.lua b/frontend/apps/filemanager/filemanagerutil.lua index af93c343e..5d7645584 100644 --- a/frontend/apps/filemanager/filemanagerutil.lua +++ b/frontend/apps/filemanager/filemanagerutil.lua @@ -18,6 +18,8 @@ function filemanagerutil.getDefaultDir() return "/mnt/us/documents" elseif Device:isKobo() then return "/mnt/onboard" + elseif Device:isRemarkable() then + return "/home/root" else return "." end diff --git a/frontend/device.lua b/frontend/device.lua index e13e7c02d..ddbd4e758 100644 --- a/frontend/device.lua +++ b/frontend/device.lua @@ -26,6 +26,11 @@ local function probeDevice() return require("device/pocketbook/device") end + local remarkable_test_stat = lfs.attributes("/usr/bin/xochitl") + if remarkable_test_stat then + return require("device/remarkable/device") + end + local sony_prstux_test_stat = lfs.attributes("/etc/PRSTUX") if sony_prstux_test_stat then return require("device/sony-prstux/device") diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index 0e72d756f..5379db3d4 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -63,6 +63,7 @@ local Device = { isKindle = no, isKobo = no, isPocketBook = no, + isRemarkable = no, isSonyPRSTUX = no, isSDL = no, isEmulator = no, diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua new file mode 100644 index 000000000..35eff0d73 --- /dev/null +++ b/frontend/device/remarkable/device.lua @@ -0,0 +1,116 @@ +local Generic = require("device/generic/device") -- <= look at this file! +local logger = require("logger") +local TimeVal = require("ui/timeval") +local ffi = require("ffi") + +local function yes() return true end +local function no() return false end + +local Remarkable = Generic:new{ + model = "reMarkable", + isRemarkable = yes, + hasKeys = yes, + hasOTAUpdates = yes, + canReboot = yes, + canPowerOff = yes, + isTouchDevice = yes, + hasKeys = yes, + hasFrontlight = no, + display_dpi = 226, +} + +local EV_ABS = 3 +local ABS_X = 00 +local ABS_Y = 01 +local ABS_MT_POSITION_X = 53 +local ABS_MT_POSITION_Y = 54 +-- Resolutions from libremarkable src/framebuffer/common.rs +local mt_width = 767 +local mt_height = 1023 +local mt_scale_x = 1404 / mt_width +local mt_scale_y = 1872 / mt_height +local adjustTouchEvt = function(self, ev) + if ev.type == EV_ABS then + -- Mirror X and scale up both X & Y as touch input is different res from + -- display + if ev.code == ABS_X or ev.code == ABS_MT_POSITION_X then + ev.value = (mt_width - ev.value) * mt_scale_x + end + if ev.code == ABS_Y or ev.code == ABS_MT_POSITION_Y then + ev.value = (mt_height - ev.value) * mt_scale_y + end + end +end + +function Remarkable:init() + self.screen = require("ffi/framebuffer_mxcfb"):new{device = self, debug = logger.dbg} + self.powerd = require("device/remarkable/powerd"):new{device = self} + self.input = require("device/input"):new{ + device = self, + event_map = require("device/remarkable/event_map"), + } + + self.input.open("/dev/input/event0") -- Wacom + self.input.open("/dev/input/event1") -- Touchscreen + self.input.open("/dev/input/event2") -- Buttons + self.input:registerEventAdjustHook(adjustTouchEvt) + -- USB plug/unplug, battery charge/not charging are generated as fake events + self.input.open("fake_events") + + local rotation_mode = self.screen.ORIENTATION_PORTRAIT + self.screen.native_rotation_mode = rotation_mode + self.screen.cur_rotation_mode = rotation_mode + + Generic.init(self) +end + +function Remarkable:supportsScreensaver() return true end + +function Remarkable:setDateTime(year, month, day, hour, min, sec) + if hour == nil or min == nil then return true end + local command + if year and month and day then + command = string.format("timedatectl set-time '%d-%d-%d %d:%d:%d'", year, month, day, hour, min, sec) + else + command = string.format("timedatectl set-time '%d:%d'",hour, min) + end + return os.execute(command) == 0 +end + +function Remarkable:intoScreenSaver() + local Screensaver = require("ui/screensaver") + if self.screen_saver_mode == false then + Screensaver:show() + end + self.powerd:beforeSuspend() + self.screen_saver_mode = true +end + +function Remarkable:outofScreenSaver() + if self.screen_saver_mode == true then + local Screensaver = require("ui/screensaver") + Screensaver:close() + local UIManager = require("ui/uimanager") + UIManager:nextTick(function() UIManager:setDirty("all", "full") end) + end + self.powerd:afterResume() + self.screen_saver_mode = false +end + +function Remarkable:suspend() + os.execute("systemctl suspend") +end + +function Remarkable:resume() +end + +function Remarkable:powerOff() + os.execute("systemctl poweroff") +end + +function Remarkable:reboot() + os.execute("systemctl reboot") +end + +return Remarkable + diff --git a/frontend/device/remarkable/event_map.lua b/frontend/device/remarkable/event_map.lua new file mode 100644 index 000000000..2566f4e5f --- /dev/null +++ b/frontend/device/remarkable/event_map.lua @@ -0,0 +1,7 @@ +return { + [102] = "Home", + [105] = "LPgBack", + [106] = "RPgFwd", + [116] = "Power", +} +-- TODO libremarkable has 143 as "wakeup" - don't know what that corresponds to? diff --git a/frontend/device/remarkable/powerd.lua b/frontend/device/remarkable/powerd.lua new file mode 100644 index 000000000..402a78f58 --- /dev/null +++ b/frontend/device/remarkable/powerd.lua @@ -0,0 +1,30 @@ +local BasePowerD = require("device/generic/powerd") + +-- TODO older firmware doesn't have the -0 on the end of the file path +local base_path = '/sys/class/power_supply/bq27441-0/' + +local Remarkable_PowerD = BasePowerD:new{ + is_charging = nil, + capacity_file = base_path .. 'capacity', + status_file = base_path .. 'status' +} + +function Remarkable_PowerD:init() +end + +function Remarkable_PowerD:frontlightIntensityHW() + return 0 +end + +function Remarkable_PowerD:setIntensityHW(intensity) +end + +function Remarkable_PowerD:getCapacityHW() + return self:read_int_file(self.capacity_file) +end + +function Remarkable_PowerD:isChargingHW() + return self:read_str_file(self.status_file) == "Charging\n" +end + +return Remarkable_PowerD diff --git a/frontend/ui/otamanager.lua b/frontend/ui/otamanager.lua index 035d7e0ad..0db71edfd 100644 --- a/frontend/ui/otamanager.lua +++ b/frontend/ui/otamanager.lua @@ -127,6 +127,8 @@ function OTAManager:getOTAModel() return "kobo" elseif Device:isPocketBook() then return "pocketbook" + elseif Device:isRemarkable() then + return "remarkable" elseif Device:isSonyPRSTUX() then return "sony-prstux" else diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 672771e89..150583341 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -192,6 +192,37 @@ function UIManager:init() Device:usbPlugOut() self:_afterNotCharging() end + elseif Device:isRemarkable() then + self.event_handlers["PowerPress"] = function() + UIManager:scheduleIn(2, self.poweroff_action) + end + self.event_handlers["PowerRelease"] = function() + if not self._entered_poweroff_stage then + UIManager:unschedule(self.poweroff_action) + -- resume if we were suspended + if Device.screen_saver_mode then + self:resume() + else + self:suspend() + end + end + end + self.event_handlers["Suspend"] = function() + self:_beforeSuspend() + Device:intoScreenSaver() + Device:suspend() + end + self.event_handlers["Resume"] = function() + Device:resume() + Device:outofScreenSaver() + self:_afterResume() + end + self.event_handlers["__default__"] = function(input_event) + -- Same as in Kobo: we want to ignore keys during suspension + if not Device.screen_saver_mode then + self:sendEvent(input_event) + end + end elseif Device:isSonyPRSTUX() then self.event_handlers["PowerPress"] = function() UIManager:scheduleIn(2, self.poweroff_action) @@ -1155,7 +1186,7 @@ end -- Executes all the operations of a suspending request. This function usually puts the device into -- suspension. function UIManager:suspend() - if Device:isCervantes() or Device:isKobo() or Device:isSDL() or Device:isSonyPRSTUX() then + if Device:isCervantes() or Device:isKobo() or Device:isSDL() or Device:isRemarkable() or Device:isSonyPRSTUX() then self.event_handlers["Suspend"]() elseif Device:isKindle() then Device.powerd:toggleSuspend() @@ -1164,7 +1195,7 @@ end -- Executes all the operations of a resume request. This function usually wakes up the device. function UIManager:resume() - if Device:isCervantes() or Device:isKobo() or Device:isSDL() or Device:isSonyPRSTUX() then + if Device:isCervantes() or Device:isKobo() or Device:isSDL() or Device:isRemarkable() or Device:isSonyPRSTUX() then self.event_handlers["Resume"]() elseif Device:isKindle() then self.event_handlers["OutOfSS"]() diff --git a/kodev b/kodev index 528c5768d..b8ec31502 100755 --- a/kodev +++ b/kodev @@ -116,6 +116,7 @@ SUPPORTED_TARGETS=" kindle-legacy Needed only for Kindle2/3/DXG kobo cervantes + remarkable sony-prstux android pocketbook @@ -187,6 +188,10 @@ ${SUPPORTED_TARGETS}" make TARGET=kobo assert_ret_zero $? ;; + remarkable) + make TARGET=remarkable + assert_ret_zero $? + ;; sony-prstux) make TARGET=sony-prstux assert_ret_zero $? @@ -303,6 +308,9 @@ ${SUPPORTED_TARGETS}" kobo) make TARGET=kobo clean ;; + remarkable) + make TARGET=remarkable clean + ;; sony-prstux) make TARGET=sony-prstux clean ;; @@ -413,6 +421,10 @@ ${SUPPORTED_RELEASE_TARGETS}" kodev-build kobo make TARGET=kobo update ;; + remarkable) + kodev-build remarkable + make TARGET=remarkable update + ;; sony-prstux) kodev-build sony-prstux make TARGET=sony-prstux update diff --git a/platform/remarkable/README_remarkable.txt b/platform/remarkable/README_remarkable.txt new file mode 100644 index 000000000..bfa15a9f1 --- /dev/null +++ b/platform/remarkable/README_remarkable.txt @@ -0,0 +1,24 @@ +# General + +When connected to WiFi you can find the IP address and root password for your +reMarkable in Settings -> About, then scroll down the GPLv3 compliance on the +right (finger drag scroll, not the page forward/back buttons). This should also +work for the USB network you get if you connect the reMarkable to your computer +with a USB cable. + +# Install + + scp .zip root@: + + ssh root@ + + unzip .zip + cp -v koreader/*.service /etc/systemd/system/ + systemctl enable --now button-listen + +Hold down the middle button for 3 seconds to start koreader. To return to +xochitl just exit koreader (swipe down from the top of the screen, select icon +in the top right, Exit, Exit). + +Some reMarkable software updates will wipe the new systemd units so you will have +to run the last two steps again when that happens. diff --git a/platform/remarkable/button-listen.service b/platform/remarkable/button-listen.service new file mode 100644 index 000000000..2290016a5 --- /dev/null +++ b/platform/remarkable/button-listen.service @@ -0,0 +1,14 @@ +[Unit] +Description=KOReader Button Listener +StartLimitIntervalSec=600 +StartLimitBurst=4 +After=home.mount + +[Service] +ExecStart=/home/root/koreader/button-listen +Restart=on-failure + +[Install] +WantedBy=multi-user.target + + diff --git a/platform/remarkable/koreader.service b/platform/remarkable/koreader.service new file mode 100644 index 000000000..1f9ef90df --- /dev/null +++ b/platform/remarkable/koreader.service @@ -0,0 +1,18 @@ +[Unit] +Description=KOReader +StartLimitIntervalSec=600 +StartLimitBurst=4 +After=home.mount +After=xochitl.service +Conflicts=xochitl.service +OnFailure=xochitl.service + +[Service] +ExecStart=/home/root/koreader/koreader.sh +ExecStopPost=/bin/false +Restart=no + +[Install] +WantedBy=multi-user.target + + diff --git a/platform/remarkable/koreader.sh b/platform/remarkable/koreader.sh new file mode 100755 index 000000000..6daa4d8ea --- /dev/null +++ b/platform/remarkable/koreader.sh @@ -0,0 +1,233 @@ +#!/bin/sh +export LC_ALL="en_US.UTF-8" + +# working directory of koreader +KOREADER_DIR="${0%/*}" + +# we're always starting from our working directory +cd "${KOREADER_DIR}" || exit + +# update to new version from OTA directory +ko_update_check() { + NEWUPDATE="${KOREADER_DIR}/ota/koreader.updated.tar" + INSTALLED="${KOREADER_DIR}/ota/koreader.installed.tar" + if [ -f "${NEWUPDATE}" ]; then + # If button-listen service is running then stop it during update so that + # the update can overwite the binary + systemctl is-active --quiet button-listen + USING_BUTTON_LISTEN=$? + if [ ${USING_BUTTON_LISTEN} -eq 0 ]; then + systemctl stop button-listen + fi + + ./fbink -q -y -7 -pmh "Updating KOReader" + # NOTE: See frontend/ui/otamanager.lua for a few more details on how we squeeze a percentage out of tar's checkpoint feature + # NOTE: %B should always be 512 in our case, so let stat do part of the maths for us instead of using %s ;). + FILESIZE="$(stat -c %b "${NEWUPDATE}")" + BLOCKS="$((FILESIZE / 20))" + export CPOINTS="$((BLOCKS / 100))" + # shellcheck disable=SC2016 + ./tar xf "${NEWUPDATE}" --strip-components=1 --no-same-permissions --no-same-owner --checkpoint="${CPOINTS}" --checkpoint-action=exec='./fbink -q -y -6 -P $(($TAR_CHECKPOINT/$CPOINTS))' + fail=$? + # Cleanup behind us... + if [ "${fail}" -eq 0 ]; then + mv "${NEWUPDATE}" "${INSTALLED}" + ./fbink -q -y -6 -pm "Update successful :)" + ./fbink -q -y -5 -pm "KOReader will start momentarily . . ." + else + # Uh oh... + ./fbink -q -y -6 -pmh "Update failed :(" + ./fbink -q -y -5 -pm "KOReader may fail to function properly!" + fi + rm -f "${NEWUPDATE}" # always purge newupdate in all cases to prevent update loop + unset BLOCKS CPOINTS + # Ensure everything is flushed to disk before we restart. This *will* stall for a while on slow storage! + sync + + if [ ${USING_BUTTON_LISTEN} -eq 0 ]; then + systemctl start button-listen + fi + fi +} +# NOTE: Keep doing an initial update check, in addition to one during the restart loop, so we can pickup potential updates of this very script... +ko_update_check +# If an update happened, and was successful, reexec +if [ -n "${fail}" ] && [ "${fail}" -eq 0 ]; then + # By now, we know we're in the right directory, and our script name is pretty much set in stone, so we can forgo using $0 + exec ./koreader.sh "${@}" +fi + +# load our own shared libraries if possible +export LD_LIBRARY_PATH="${KOREADER_DIR}/libs:${LD_LIBRARY_PATH}" + +# export trained OCR data directory +export TESSDATA_PREFIX="data" + +# export dict directory +export STARDICT_DATA_DIR="data/dict" + +# export external font directory +export EXT_FONT_DIR="/usr/share/fonts/ttf;/usr/share/fonts/opentype" + +# We'll want to ensure Portrait rotation to allow us to use faster blitting codepaths @ 8bpp, +# so remember the current one before fbdepth does its thing. +ORIG_FB_ROTA="$(./fbdepth -o)" +# In the same vein, swap to 8bpp, +# because 16bpp is the worst idea in the history of time, as RGB565 is generally a PITA without hardware blitting, +# and 32bpp usually gains us nothing except a performance hit (we're not Qt5 with its QPainter constraints). +# The reduced size & complexity should hopefully make things snappier, +# (and hopefully prevent the JIT from going crazy on high-density screens...). +# NOTE: Even though both pickel & Nickel appear to restore their preferred fb setup, we'll have to do it ourselves, +# as they fail to flip the grayscale flag properly. Plus, we get to play nice with every launch method that way. +# So, remember the current bitdepth, so we can restore it on exit. +ORIG_FB_BPP="$(./fbdepth -g)" +echo "Original fb settings: bitdepth = ${ORIG_FB_BPP}, rotation = ${ORIG_FB_ROTA}" >>crash.log 2>&1 + +# Sanity check... +case "${ORIG_FB_BPP}" in + 16) ;; + 32) ;; + *) + # Uh oh? Don't do anything... + unset ORIG_FB_BPP + ;; +esac + +# The actual swap is done in a function, because we can disable it in the Developer settings, and we want to honor it on restart. +ko_do_fbdepth() { + # Check if the swap has been disabled... + if grep -q '\["dev_startup_no_fbdepth"\] = true' 'settings.reader.lua' 2>/dev/null; then + # Swap back to the original bitdepth (in case this was a restart) + if [ -n "${ORIG_FB_BPP}" ]; then + + echo "Making sure we're using the original fb bitdepth @ ${ORIG_FB_BPP}bpp & rotation @ ${ORIG_FB_ROTA}" >>crash.log 2>&1 + ./fbdepth -d "${ORIG_FB_BPP}" -r "${ORIG_FB_ROTA}" >>crash.log 2>&1 + fi + else + # Swap to 8bpp if things look sane + if [ -n "${ORIG_FB_BPP}" ]; then + echo "Switching fb bitdepth to 8bpp & rotation to Portrait" >>crash.log 2>&1 + ./fbdepth -d 8 -r -1 >>crash.log 2>&1 + fi + fi +} + +# we keep at most 500KB worth of crash log +if [ -e crash.log ]; then + tail -c 500000 crash.log >crash.log.new + mv -f crash.log.new crash.log +fi + +CRASH_COUNT=0 +CRASH_TS=0 +CRASH_PREV_TS=0 +# Because we *want* an initial fbdepth pass ;). +RETURN_VALUE=85 +while [ ${RETURN_VALUE} -ne 0 ]; do + # 85 is what we return when asking for a KOReader restart + if [ ${RETURN_VALUE} -eq 85 ]; then + # Do an update check now, so we can actually update KOReader via the "Restart KOReader" menu entry ;). + ko_update_check + # Do or double-check the fb depth switch, or restore original bitdepth if requested + ko_do_fbdepth + fi + + ./reader.lua >>crash.log 2>&1 + RETURN_VALUE=$? + + # Did we crash? + if [ ${RETURN_VALUE} -ne 0 ] && [ ${RETURN_VALUE} -ne 85 ]; then + # Increment the crash counter + CRASH_COUNT=$((CRASH_COUNT + 1)) + CRASH_TS=$(date +'%s') + # Reset it to a first crash if it's been a while since our last crash... + if [ $((CRASH_TS - CRASH_PREV_TS)) -ge 20 ]; then + CRASH_COUNT=1 + fi + + # Check if the user requested to always abort on crash + if grep -q '\["dev_abort_on_crash"\] = true' 'settings.reader.lua' 2>/dev/null; then + ALWAYS_ABORT="true" + # In which case, make sure we pause on *every* crash + CRASH_COUNT=1 + else + ALWAYS_ABORT="false" + fi + + # Show a fancy bomb on screen + viewWidth=600 + viewHeight=800 + FONTH=16 + eval "$(./fbink -e | tr ';' '\n' | grep -e viewWidth -e viewHeight -e FONTH | tr '\n' ';')" + # Compute margins & sizes relative to the screen's resolution, so we end up with a similar layout, no matter the device. + # Height @ ~56.7%, w/ a margin worth 1.5 lines + bombHeight=$((viewHeight / 2 + viewHeight / 15)) + bombMargin=$((FONTH + FONTH / 2)) + # With a little notice at the top of the screen, on a big gray screen of death ;). + ./fbink -q -b -c -B GRAY9 -m -y 1 "Don't Panic! (Crash n°${CRASH_COUNT} -> ${RETURN_VALUE})" + if [ ${CRASH_COUNT} -eq 1 ]; then + # Warn that we're waiting on a tap to continue... + ./fbink -q -b -O -m -y 2 "Tap the screen to continue." + fi + # U+1F4A3, the hard way, because we can't use \u or \U escape sequences... + # shellcheck disable=SC2039 + ./fbink -q -b -O -m -t regular=./fonts/freefont/FreeSerif.ttf,px=${bombHeight},top=${bombMargin} $'\xf0\x9f\x92\xa3' + # And then print the tail end of the log on the bottom of the screen... + crashLog="$(tail -n 25 crash.log | sed -e 's/\t/ /g')" + # The idea for the margins being to leave enough room for an fbink -Z bar, small horizontal margins, and a font size based on what 6pt looked like @ 265dpi + ./fbink -q -b -O -t regular=./fonts/droid/DroidSansMono.ttf,top=$((viewHeight / 2 + FONTH * 2 + FONTH / 2)),left=$((viewWidth / 60)),right=$((viewWidth / 60)),px=$((viewHeight / 64)) "${crashLog}" + # So far, we hadn't triggered an actual screen refresh, do that now, to make sure everything is bundled in a single flashing refresh. + ./fbink -q -f -s + # Cue a lemming's faceplant sound effect! + + { + echo "!!!!" + echo "Uh oh, something went awry... (Crash n°${CRASH_COUNT}: $(date +'%x @ %X'))" + echo "Running on Linux $(uname -r) ($(uname -v))" + } >>crash.log 2>&1 + if [ ${CRASH_COUNT} -lt 5 ] && [ "${ALWAYS_ABORT}" = "false" ]; then + echo "Attempting to restart KOReader . . ." >>crash.log 2>&1 + echo "!!!!" >>crash.log 2>&1 + fi + + # Pause a bit if it's the first crash in a while, so that it actually has a chance of getting noticed ;). + if [ ${CRASH_COUNT} -eq 1 ]; then + # NOTE: We don't actually care about what read read, we're just using it as a fancy sleep ;). + # i.e., we pause either until the 15s timeout, or until the user touches the screen. + # shellcheck disable=SC2039 + read -r -t 15 >crash.log 2>&1 + echo "!!!! ! !!!!" >>crash.log 2>&1 + break + fi + + # If the user requested to always abort on crash, do so. + if [ "${ALWAYS_ABORT}" = "true" ]; then + echo "Aborting . . ." >>crash.log 2>&1 + echo "!!!! ! !!!!" >>crash.log 2>&1 + break + fi + else + # Reset the crash counter if that was a sane exit/restart + CRASH_COUNT=0 + fi +done + +# Restore original fb bitdepth if need be... +# Since we also (almost) always enforce Portrait, we also have to restore the original rotation no matter what ;). +if [ -n "${ORIG_FB_BPP}" ]; then + echo "Restoring original fb bitdepth @ ${ORIG_FB_BPP}bpp & rotation @ ${ORIG_FB_ROTA}" >>crash.log 2>&1 + ./fbdepth -d "${ORIG_FB_BPP}" -r "${ORIG_FB_ROTA}" >>crash.log 2>&1 +else + echo "Restoring original fb rotation @ ${ORIG_FB_ROTA}" >>crash.log 2>&1 + ./fbdepth -r "${ORIG_FB_ROTA}" >>crash.log 2>&1 +fi + +exit ${RETURN_VALUE}