Release v0.6.8: add background image OCR and optional Tesseract

This commit is contained in:
2026-10-29 21:40:29 +02:00
parent 95c7199027
commit 107d2f8852
21 changed files with 590 additions and 37 deletions
+92 -1
View File
@@ -269,8 +269,15 @@ App::~App()
std::lock_guard lock(jobsMutex);
stop = true;
jobs.clear();
pendingHistorySearch = {};
}
jobsReady.notify_all();
historySearchReady.notify_all();
historyOcrReady.notify_all();
if (historyOcrWorker.joinable())
historyOcrWorker.join();
if (historySearchWorker.joinable())
historySearchWorker.join();
if (worker.joinable())
worker.join();
RemoveClipboardFormatListener(window);
@@ -291,6 +298,83 @@ void App::enqueue(std::function<void()> job)
}
jobsReady.notify_one();
}
void App::enqueueHistorySearch(std::function<void()> job)
{
{
std::lock_guard lock(jobsMutex);
if (stop) return;
// Only the latest query matters. Clipboard capture and OCR must not delay it.
pendingHistorySearch = std::move(job);
if (!historySearchWorker.joinable())
historySearchWorker = std::thread([this]
{
for (;;)
{
std::function<void()> searchJob;
{
std::unique_lock lock(jobsMutex);
historySearchReady.wait(lock, [&] { return stop || bool(pendingHistorySearch); });
if (stop) return;
searchJob = std::move(pendingHistorySearch);
pendingHistorySearch = {};
}
try { searchJob(); }
catch (const std::exception &e) { report(wide(e.what())); }
}
});
}
historySearchReady.notify_one();
}
void App::scheduleHistoryOcr(const Settings &config)
{
{
std::lock_guard lock(jobsMutex);
if (stop || !history || !config.history) return;
historyOcrSettings = config;
historyOcrRequested = true;
if (!historyOcrWorker.joinable())
historyOcrWorker = std::thread([this]
{
for (;;)
{
Settings config;
{
std::unique_lock lock(jobsMutex);
historyOcrReady.wait(lock, [&] { return stop || historyOcrRequested; });
if (stop) return;
historyOcrRequested = false;
config = historyOcrSettings;
}
for (const auto &entry : history->entries())
{
{ std::lock_guard lock(jobsMutex); if (stop) return; }
auto profile = ocrProfile(config);
if (!entry.image || (entry.ocrState != 1 && entry.ocrProfile == profile)) continue;
try
{
auto text = recognizeText(history->load(entry.id), config);
std::lock_guard lock(jobsMutex);
if (stop || ocrProfile(historyOcrSettings) != profile) continue;
if (history->saveOcr(entry.id, text, config))
PostMessageW(window, HistoryMessage, 0, 0);
}
catch (const std::exception &e)
{
// Automatic OCR never changes the clipboard or opens error dialogs.
log(L"History OCR: " + wide(e.what()));
try {
std::lock_guard lock(jobsMutex);
if (!stop && ocrProfile(historyOcrSettings) == profile)
history->saveOcr(entry.id, std::nullopt, config);
}
catch (const std::exception &saveError) { log(L"History OCR save: " + wide(saveError.what())); }
}
}
}
});
}
historyOcrReady.notify_one();
}
void App::report(const std::wstring &text)
{
log(text);
@@ -333,6 +417,7 @@ void App::applySettings(const Settings &s)
{
bool autostartChanged = false;
validateSettings(s);
validateTesseract(s);
if (s.autoCorrect)
{
AutoCorrector probe;
@@ -375,6 +460,7 @@ void App::applySettings(const Settings &s)
}
// Clear on the worker so the next check reloads edits to dictionary files.
enqueue([this] { autoCorrector.configure({}, {}); });
scheduleHistoryOcr(s);
SetThreadExecutionState(ES_CONTINUOUS |
(settings.awake ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0));
}
@@ -1051,6 +1137,7 @@ void App::captureHistory()
!f.data[1] && !f.data[2] && !f.data[3])
return;
history->add(clip, app, config);
scheduleHistoryOcr(config);
PostMessageW(window, HistoryMessage, 0, 0);
}
catch (const std::exception &e)
@@ -1078,7 +1165,9 @@ void App::recognizeEntry(std::wstring id)
} reset{busy};
try
{
auto text = recognizeText(history->load(id));
auto cached = history->ocrText(id, ocrProfile(config));
auto text = cached ? *cached : recognizeText(history->load(id), config);
if (!cached) history->saveOcr(id, text, config);
if (text.empty())
{
report(L"No text was found in the image.");
@@ -1442,6 +1531,8 @@ int App::execute()
if (!keyboardHook || !mouseHook)
report(L"Input tracking could not start. Automatic recent-text conversion is unavailable.");
AddClipboardFormatListener(window);
// Resume pending OCR, including older images without a saved OCR state.
scheduleHistoryOcr(settings);
addTray();
SetTimer(window, 1, 60000, nullptr);
SetThreadExecutionState(ES_CONTINUOUS |