Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ The Settings screen allows you to configure the device's behavior. There are a f

- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
- "Page Scroll" - Long-pressing scrolls a page up/down
- **Long-press Menu**: Selects the function bound to holding the menu button (Confirm) while reading an EPUB. **Cycles through the available functions** each time the setting is selected — additional functions may be added in future releases, so this is not a binary on/off toggle. A short press of Confirm always opens the reader menu as normal:
- "Bookmark" (default) - Hold Confirm (~0.4 second) to drop a bookmark at the current page.
- "KOSync" - Hold Confirm (~1 second) to launch KOReader sync directly.
- "Disabled" - Long-press is ignored; only short-press opens the reader menu.

- **Short Power Button Click**: Controls the effect of a short click of the power button:

Expand Down Expand Up @@ -528,6 +532,7 @@ When reading an EPUB that contains footnotes, you can navigate to the footnote t
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
* **Reader Menu:** Press **Confirm** to open the **[Reader Menu](#5-reader-menu)**, which includes chapter navigation, reading options, and more.
* **Long-press Confirm (configurable):** Holding **Confirm** runs the function chosen by the **Long-press Menu** setting in **[Controls Settings](#363-controls)** — "Bookmark" (default) drops a bookmark, "KOSync" launches KOReader Sync, "Disabled" does nothing. A short press always opens the Reader Menu.

### Supported Languages

Expand Down Expand Up @@ -574,9 +579,9 @@ Accessible by selecting **Chapters** from the Reader Menu.

Bookmarks can be created to quickly save and restore your place in a book.

To create a bookmark, hold **Confirm** for 1 second while inside a book. A popup will appear letting you know a bookmark was created. The popup message will automatically disappear in a couple of seconds.
To create a bookmark, hold **Confirm** for about half a second while inside a book. A popup will appear letting you know a bookmark was created. The popup message will automatically disappear in a couple of seconds.

To open bookmarks, press **Confirm** while inside a book. Then navigate to the **Bookmarks** menu. Bookmarks can be opened by navigating to them and pressing **Confirm**, which will redirect you to that place in the book. You can delete bookmarks by holding **Confirm** for 1 second, and then pressing **Confirm** again to confirm deletion, or **Back** to cancel.
To open bookmarks, press **Confirm** while inside a book. Then navigate to the **Bookmarks** menu. Bookmarks can be opened by navigating to them and pressing **Confirm**, which will redirect you to that place in the book. You can delete bookmarks by holding **Confirm** for about 0.7 seconds, and then pressing **Confirm** again to confirm deletion, or **Back** to cancel.

Bookmarks are stored in the `.crosspoint/bookmarks` folder in the JSON format.

Expand Down
Binary file added docs/images/wifi/webserver_upload_dragdrop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions lib/I18n/translations/english.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
STR_LONG_PRESS_MENU: "Long-press Menu"
STR_FONT_FAMILY: "Reader Font Family"
STR_FONT_SIZE: "Reader Font Size"
STR_LINE_SPACING: "Reader Line Spacing"
Expand Down Expand Up @@ -140,6 +141,8 @@ STR_INVERTED: "Inverted"
STR_LANDSCAPE_CCW: "Landscape CCW"
STR_PREV_NEXT: "Prev/Next"
STR_NEXT_PREV: "Next/Prev"
STR_KOSYNC: "KOSync"
STR_BOOKMARK_OPTION: "Bookmark"
STR_DISABLED: "Disabled"
STR_NOTO_SERIF: "Noto Serif"
STR_NOTO_SANS: "Noto Sans"
Expand Down
14 changes: 14 additions & 0 deletions src/CrossPointSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ class CrossPointSettings {
// Short power button press actions
enum SHORT_PWRBTN { IGNORE = 0, SLEEP = 1, PAGE_TURN = 2, FORCE_REFRESH = 3, FOOTNOTES = 4, SHORT_PWRBTN_COUNT };

// Long-press Confirm action while reading an EPUB. The setting cycles through these values.
// Persisted in settings.json by index: any new function (e.g. dictionary, bookmark) MUST use a
// value >= 2 and be appended at the END of the enumValues array in SettingsList.h, otherwise the
// stored indices shift and existing saves are silently misinterpreted.
enum LONG_PRESS_MENU_FUNCTION {
LP_MENU_KOSYNC = 0,
LP_MENU_DISABLED = 1,
LP_MENU_BOOKMARK = 2,
LONG_PRESS_MENU_FUNCTION_COUNT
};

// Hide battery percentage
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT };

Expand Down Expand Up @@ -226,6 +237,9 @@ class CrossPointSettings {
uint8_t hideBatteryPercentage = HIDE_NEVER;
// Long-press page turn button behavior
uint8_t longPressButtonBehavior = OFF;
// Long-press Confirm function in EPUB reader (cycles through LONG_PRESS_MENU_FUNCTION values).
// Defaults to Bookmark to preserve the upstream long-press-Confirm-adds-bookmark behavior.
uint8_t longPressMenuFunction = LP_MENU_BOOKMARK;
// UI Theme
uint8_t uiTheme = LYRA;
// Sunlight fading compensation
Expand Down
4 changes: 4 additions & 0 deletions src/SettingsList.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,16 @@ inline std::vector<SettingInfo> getSettingsList(const SdCardFontRegistry* regist
{StrId::STR_LONG_PRESS_BEHAVIOR_OFF, StrId::STR_LONG_PRESS_BEHAVIOR_SKIP,
StrId::STR_LONG_PRESS_BEHAVIOR_ORIENTATION},
"longPressButtonBehavior", StrId::STR_CAT_CONTROLS),
SettingInfo::Enum(StrId::STR_LONG_PRESS_MENU, &CrossPointSettings::longPressMenuFunction,
{StrId::STR_KOSYNC, StrId::STR_DISABLED, StrId::STR_BOOKMARK_OPTION}, "longPressMenuFunction",
StrId::STR_CAT_CONTROLS),
SettingInfo::Enum(
StrId::STR_SHORT_PWR_BTN, &CrossPointSettings::shortPwrBtn,
{StrId::STR_IGNORE, StrId::STR_SLEEP, StrId::STR_PAGE_TURN, StrId::STR_FORCE_REFRESH, StrId::STR_FOOTNOTES},
"shortPwrBtn", StrId::STR_CAT_CONTROLS),
SettingInfo::Toggle(StrId::STR_PWR_BTN_FOOTNOTE_BACK, &CrossPointSettings::pwrBtnFootnoteBack,
"pwrBtnFootnoteBack", StrId::STR_CAT_CONTROLS),

// --- System ---
SettingInfo::Value(
StrId::STR_TIME_TO_SLEEP, &CrossPointSettings::sleepTimeoutMinutes,
Expand Down
131 changes: 78 additions & 53 deletions src/activities/reader/EpubReaderActivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ void EpubReaderActivity::loop() {
requestUpdate();
}

// Enter reader menu activity.
// Enter reader menu activity on short-press Confirm. A long-press that fired a bound
// function (bookmark or KOReader sync) sets ignoreNextConfirmRelease so the release
// following the hold does not also open the menu.
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (ignoreNextConfirmRelease) {
ignoreNextConfirmRelease = false;
Expand Down Expand Up @@ -284,14 +286,32 @@ void EpubReaderActivity::loop() {
}
}

if (mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
mappedInput.getHeldTime() >= ReaderUtils::BOOKMARK_HOLD_MS) {
if (!showBookmarkMessage) {
addBookmark();
showBookmarkMessage = true;
ignoreNextConfirmRelease = true; // Prevent accidental menu open after adding bookmark
bookmarkMessageTime = millis();
requestUpdate();
// Long-press Confirm runs the user-selected function (SETTINGS.longPressMenuFunction).
if (mappedInput.isPressed(MappedInputManager::Button::Confirm)) {
switch (SETTINGS.longPressMenuFunction) {
case CrossPointSettings::LP_MENU_BOOKMARK:
// Hold ~0.4s drops a bookmark at the current page.
if (mappedInput.getHeldTime() >= ReaderUtils::BOOKMARK_HOLD_MS && !showBookmarkMessage) {
addBookmark();
showBookmarkMessage = true;
ignoreNextConfirmRelease = true; // Prevent accidental menu open after adding bookmark
bookmarkMessageTime = millis();
requestUpdate();
}
break;
case CrossPointSettings::LP_MENU_KOSYNC:
// Hold ~1s launches KOReader sync. If sync can't run (no credentials stored), fall
// through so the normal Confirm-release still opens the reader menu.
if (mappedInput.getHeldTime() >= ReaderUtils::GO_HOME_MS) {
if (launchKOReaderSync()) {
ignoreNextConfirmRelease = true; // sync launched or error shown; suppress menu open
return;
}
}
break;
case CrossPointSettings::LP_MENU_DISABLED:
default:
break;
}
}

Expand Down Expand Up @@ -578,50 +598,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
break;
}
case EpubReaderMenuActivity::MenuAction::SYNC: {
if (KOREADER_STORE.hasCredentials()) {
const int currentPage = section ? section->currentPage : nextPageNumber;
const int totalPages = section ? section->pageCount : cachedChapterTotalPageCount;
std::optional<uint16_t> paragraphIndex;
if (section && currentPage >= 0 && currentPage < section->pageCount) {
const uint16_t paragraphPage =
currentPage > 0 ? static_cast<uint16_t>(currentPage - 1) : static_cast<uint16_t>(currentPage);
if (const auto pIdx = section->getParagraphIndexForPage(paragraphPage)) {
paragraphIndex = *pIdx;
}
}

// Pre-compute local KO position and chapter name while Epub is still in RAM.
CrossPointPosition localPos = getCurrentPosition();
SavedProgressPosition localKoPos = ProgressMapper::toSavedProgress(epub, localPos);
const int tocIdx = epub->getTocIndexForSpineIndex(currentSpineIndex);
std::string localChapterName = (tocIdx >= 0) ? epub->getTocItem(tocIdx).title : "";
const std::string savedEpubPath = epub->getPath();

// Persist current position so the reader resumes at the right page on return.
// goToReader() depends on this file, so abort the sync if the write fails.
if (!saveProgress(currentSpineIndex, currentPage, totalPages)) {
LOG_ERR("KOSync", "Aborting sync because current progress could not be saved");
pendingSyncSaveError = true;
requestUpdate();
return;
}

// Release Epub and Section to free ~65KB RAM for the TLS handshake.
LOG_DBG("KOSync", "Releasing epub for sync (heap before: %u)", (unsigned)ESP.getFreeHeap());
{
RenderLock lock(*this);
if (section) {
nextPageNumber = section->currentPage;
}
section.reset();
epub.reset();
}
LOG_DBG("KOSync", "Epub released (heap after: %u)", (unsigned)ESP.getFreeHeap());

activityManager.replaceActivity(std::make_unique<KOReaderSyncActivity>(
renderer, mappedInput, savedEpubPath, currentSpineIndex, currentPage, totalPages, std::move(localKoPos),
std::move(localChapterName), paragraphIndex));
}
launchKOReaderSync();
break;
}
case EpubReaderMenuActivity::MenuAction::BOOKMARKS: {
Expand All @@ -633,6 +610,54 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
}
}

bool EpubReaderActivity::launchKOReaderSync() {
if (!KOREADER_STORE.hasCredentials()) return false; // no-op: nothing to launch

const int currentPage = section ? section->currentPage : nextPageNumber;
const int totalPages = section ? section->pageCount : cachedChapterTotalPageCount;
std::optional<uint16_t> paragraphIndex;
if (section && currentPage >= 0 && currentPage < section->pageCount) {
const uint16_t paragraphPage =
currentPage > 0 ? static_cast<uint16_t>(currentPage - 1) : static_cast<uint16_t>(currentPage);
if (const auto pIdx = section->getParagraphIndexForPage(paragraphPage)) {
paragraphIndex = *pIdx;
}
}

// Pre-compute local KO position and chapter name while Epub is still in RAM.
CrossPointPosition localPos = getCurrentPosition();
SavedProgressPosition localKoPos = ProgressMapper::toSavedProgress(epub, localPos);
const int tocIdx = epub->getTocIndexForSpineIndex(currentSpineIndex);
std::string localChapterName = (tocIdx >= 0) ? epub->getTocItem(tocIdx).title : "";
const std::string savedEpubPath = epub->getPath();

// Persist current position so the reader resumes at the right page on return.
// goToReader() depends on this file, so abort the sync if the write fails.
if (!saveProgress(currentSpineIndex, currentPage, totalPages)) {
LOG_ERR("KOSync", "Aborting sync because current progress could not be saved");
pendingSyncSaveError = true;
requestUpdate();
return true; // acted: surfaced a save error to the user
}

// Release Epub and Section to free ~65KB RAM for the TLS handshake.
LOG_DBG("KOSync", "Releasing epub for sync (heap before: %u)", (unsigned)ESP.getFreeHeap());
{
RenderLock lock(*this);
if (section) {
nextPageNumber = section->currentPage;
}
section.reset();
epub.reset();
}
LOG_DBG("KOSync", "Epub released (heap after: %u)", (unsigned)ESP.getFreeHeap());

activityManager.replaceActivity(std::make_unique<KOReaderSyncActivity>(
renderer, mappedInput, savedEpubPath, currentSpineIndex, currentPage, totalPages, std::move(localKoPos),
std::move(localChapterName), paragraphIndex));
return true; // acted: launched the sync activity
}

void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
// No-op if the selected orientation matches current settings.
if (SETTINGS.orientation == orientation) {
Expand Down
3 changes: 3 additions & 0 deletions src/activities/reader/EpubReaderActivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class EpubReaderActivity final : public Activity {
// Jump to a percentage of the book (0-100), mapping it to spine and page.
void jumpToPercent(int percent);
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
// Returns true if sync acted (launched, or surfaced a save error); false if it was a no-op
// because no KOReader credentials are stored.
bool launchKOReaderSync();
void applyOrientation(uint8_t orientation);
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
void pageTurn(bool isForwardTurn);
Expand Down
8 changes: 5 additions & 3 deletions src/activities/util/KeyboardEntryActivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ bool KeyboardEntryActivity::handleKeyPress() {
case SpecialKeyType::Shift:
delPressCount = 0;
hintVisible = false;
if (urlMode || inputType == InputType::Url) return true;
// Shift is meaningless in the URL-snippet panel and the symbol layout, but
// must work for the URL letter layout so uppercase letters can be entered (#2178).
if (urlMode) return true;
if (symMode) return true;
shiftState = (shiftState + 1) % 2;
return true;
Expand Down Expand Up @@ -675,8 +677,8 @@ void KeyboardEntryActivity::render(RenderLock&&) {
const char* label;
};
const BottomKeyInfo bottomKeys[BOTTOM_KEY_COUNT] = {
{(symMode || urlMode || inputType == InputType::Url) ? KeyboardKeyType::Disabled : KeyboardKeyType::Shift,
(symMode || urlMode || inputType == InputType::Url) ? shiftString[0] : shiftString[shiftState]},
{(symMode || urlMode) ? KeyboardKeyType::Disabled : KeyboardKeyType::Shift,
(symMode || urlMode) ? shiftString[0] : shiftString[shiftState]},
{KeyboardKeyType::Mode, urlMode ? "abc" : (symMode ? "abc" : "#@!")},
{inputType == InputType::Url ? KeyboardKeyType::Mode : KeyboardKeyType::Space,
inputType == InputType::Url ? "URL" : nullptr},
Expand Down
Loading
Loading