diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index 4df410e..60c734f 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -1416,21 +1416,20 @@ void RenderSendTab(App* app) paste_hovered = material::IsRectHovered(pasteBtnPos, ImVec2(pasteBtnPos.x + pasteBtnSize.x, pasteBtnPos.y + pasteBtnSize.y)); - // Handle preview state — don't modify s_to_address, just store preview text - if (paste_hovered && !s_paste_previewing) { - const char* clip = ImGui::GetClipboardText(); - if (clip && clip[0] != '\0') { - std::string trimmed(clip); - while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\n' || trimmed.front() == '\r' || trimmed.front() == '\t')) - trimmed.erase(trimmed.begin()); - while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\n' || trimmed.back() == '\r' || trimmed.back() == '\t')) - trimmed.pop_back(); - // Preview any address, a mining one too: once pasted, the field explains why it can't be paid. - bool looksValid = dragonx::util::isDragonXAddress(trimmed); - if (looksValid && s_to_address[0] == '\0') { - s_preview_text = trimmed; - s_paste_previewing = true; - } + // Handle preview state — don't modify s_to_address, just store preview text. + // The OS clipboard is only worth reading when the result can latch (field empty), polled on hover entry + // and then at most every 0.5 s (SDL's clipboard is a synchronous round-trip: 1-5 ms on X11, can stall). + static double s_nextClipPoll = 0.0; + if (!paste_hovered) s_nextClipPoll = 0.0; // poll immediately on the next hover entry + if (paste_hovered && !s_paste_previewing && s_to_address[0] == '\0' && + ImGui::GetTime() >= s_nextClipPoll) { + s_nextClipPoll = ImGui::GetTime() + 0.5; + // Copy immediately: the backend frees the previous clipboard buffer on the next call. + const std::string trimmed = dragonx::util::trimmedClipboardText(ImGui::GetClipboardText()); + // Preview any address, a mining one too: once pasted, the field explains why it can't be paid. + if (!trimmed.empty() && dragonx::util::isDragonXAddress(trimmed)) { + s_preview_text = trimmed; + s_paste_previewing = true; } } else if (!paste_hovered && s_paste_previewing) { s_paste_previewing = false; diff --git a/src/util/text_format.cpp b/src/util/text_format.cpp index c6f5137..8b74dab 100644 --- a/src/util/text_format.cpp +++ b/src/util/text_format.cpp @@ -118,5 +118,18 @@ bool containsIgnoreCase(std::string_view haystack, std::string_view needle) return false; } +std::string trimmedClipboardText(const char* clip, std::size_t maxBytes) +{ + if (!clip || clip[0] == '\0') return {}; + std::size_t n = 0; + while (n <= maxBytes && clip[n] != '\0') ++n; + if (n > maxBytes) return {}; + const auto ws = [](char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }; + std::size_t b = 0, e = n; + while (b < e && ws(clip[b])) ++b; + while (e > b && ws(clip[e - 1])) --e; + return std::string(clip + b, e - b); +} + } // namespace util } // namespace dragonx diff --git a/src/util/text_format.h b/src/util/text_format.h index 0b6731c..6586c22 100644 --- a/src/util/text_format.h +++ b/src/util/text_format.h @@ -82,5 +82,9 @@ std::string truncateMiddleToFit(const std::string& s, int minFront, int minBack, return build(lo); } +// Clipboard text -> trimmed (ASCII whitespace ' ', \t, \n, \r) candidate string. Empty for a null/empty clip or one +// longer than maxBytes (an address is < 100 bytes; this bounds the copy of a pathological multi-MB clipboard). +std::string trimmedClipboardText(const char* clip, std::size_t maxBytes = 1024); + } // namespace util } // namespace dragonx diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 5868bb6..d5d11d6 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -18121,6 +18121,12 @@ void testContactsTextHelpers() { for (float maxW = -1; maxW < 700; maxW += 13.0f) EXPECT_EQ(dragonx::util::truncateMiddleToFit(s, 5, 5, maxW, meas), oldFit(s, 5, 5, maxW)); } + + EXPECT_EQ(dragonx::util::trimmedClipboardText(nullptr), std::string()); + EXPECT_EQ(dragonx::util::trimmedClipboardText(" zs1abc\n"), std::string("zs1abc")); + EXPECT_EQ(dragonx::util::trimmedClipboardText("\t\r\n "), std::string()); + EXPECT_EQ(dragonx::util::trimmedClipboardText(std::string(2000, 'a').c_str()), std::string()); + EXPECT_EQ(dragonx::util::trimmedClipboardText(std::string(1024, 'a').c_str()).size(), (size_t)1024); } } // namespace