diff --git a/src/app.cpp b/src/app.cpp index 1ea22a8..ab5da99 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -148,6 +148,7 @@ void App::wipeSecrets() sodium_memzero(&seed_backup_phrase_[0], seed_backup_phrase_.size()); sodium_memzero(export_result_, sizeof(export_result_)); // exported WIF/z-key (SECRET) sodium_memzero(import_key_input_, sizeof(import_key_input_)); // pasted private key (SECRET) + sodium_memzero(import_clip_preview_, sizeof(import_clip_preview_)); // clipboard preview (SECRET) } // Enqueue a blocking xmrig start/stop op onto the dedicated serialized control thread so the render thread @@ -3958,9 +3959,7 @@ void App::renderImportKeyDialog() if (pasteClicked) { const char* clipboard = ImGui::GetClipboardText(); if (clipboard) { - std::string trimmed(clipboard); - while (!trimmed.empty() && (trimmed.front()==' '||trimmed.front()=='\t'||trimmed.front()=='\n'||trimmed.front()=='\r')) trimmed.erase(trimmed.begin()); - while (!trimmed.empty() && (trimmed.back()==' '||trimmed.back()=='\t'||trimmed.back()=='\n'||trimmed.back()=='\r')) trimmed.pop_back(); + std::string trimmed = dragonx::util::trimmedClipboardText(clipboard); snprintf(import_key_input_, sizeof(import_key_input_), "%s", trimmed.c_str()); } } @@ -3972,27 +3971,52 @@ void App::renderImportKeyDialog() // wrong-type / red unrecognized). Drawn via the draw list only: it verifies the key WITHOUT adding // a layout row, since a hover-toggled row would grow the centered dialog and shift the button out // from under the cursor (hover flicker). - if (pasteHovered && import_key_input_[0] == '\0') { - const char* clip = ImGui::GetClipboardText(); - if (clip && clip[0]) { - std::string t(clip); - while (!t.empty() && (t.front()==' '||t.front()=='\t'||t.front()=='\n'||t.front()=='\r')) t.erase(t.begin()); - while (!t.empty() && (t.back()==' '||t.back()=='\t'||t.back()=='\n'||t.back()=='\r')) t.pop_back(); - if (!t.empty()) { - const bool pView = services::WalletSecurityController::isViewingKey(t); - const bool pSpend = services::WalletSecurityController::isRecognizedPrivateKey(t); - const bool pOk = viewMode ? pView : pSpend; - const bool pWrong = viewMode ? pSpend : pView; - ImVec4 col = pOk ? m::SuccessVec4() - : pWrong ? ImVec4(0.8f, 0.6f, 0.0f, 1.0f) - : m::ErrorVec4(); - col.w = 0.6f; // ghost (uncommitted) preview - std::string shown = import_key_reveal_ ? t : std::string(std::min(t.size(), (size_t)56), '*'); - if (shown.size() > 48) shown.resize(48); // fits the field before the flush eye button - const ImVec2 tp(fieldMin.x + ImGui::GetStyle().FramePadding.x, - fieldMin.y + ImGui::GetStyle().FramePadding.y); - ImGui::GetWindowDrawList()->AddText(tp, ImGui::ColorConvertFloat4ToU32(col), shown.c_str()); - } + const double nowClip = ImGui::GetTime(); + const bool hoverEnter = pasteHovered && !import_clip_hover_prev_; + import_clip_hover_prev_ = pasteHovered; + + if (pasteHovered && import_key_input_[0] == '\0' && + (hoverEnter || nowClip >= import_clip_next_at_)) { + import_clip_next_at_ = nowClip + 0.5; // SDL clipboard is a synchronous round-trip + // Copy immediately - the backend buffer is freed on the next GetClipboardText() call. + std::string t = dragonx::util::trimmedClipboardText(ImGui::GetClipboardText()); + if (!t.empty()) { + const bool pView = services::WalletSecurityController::isViewingKey(t); + const bool pSpend = services::WalletSecurityController::isRecognizedPrivateKey(t); + const bool pOk = viewMode ? pView : pSpend; + const bool pWrong = viewMode ? pSpend : pView; + import_clip_class_ = pOk ? 1 : pWrong ? 2 : 3; + snprintf(import_clip_preview_, sizeof(import_clip_preview_), "%.48s", t.c_str()); + sodium_memzero(&t[0], t.size()); // wipe the transient clipboard copy + } else { + import_clip_class_ = 0; + sodium_memzero(import_clip_preview_, sizeof(import_clip_preview_)); + } + } + + if (pasteHovered && import_key_input_[0] == '\0' && import_clip_class_ != 0) { + ImVec4 col = import_clip_class_ == 1 ? m::SuccessVec4() + : import_clip_class_ == 2 ? ImVec4(0.8f, 0.6f, 0.0f, 1.0f) + : m::ErrorVec4(); + col.w = 0.6f; // ghost (uncommitted) preview + const size_t plen = std::strlen(import_clip_preview_); + const ImVec2 tp(fieldMin.x + ImGui::GetStyle().FramePadding.x, + fieldMin.y + ImGui::GetStyle().FramePadding.y); + if (import_key_reveal_) { + ImGui::GetWindowDrawList()->AddText(tp, ImGui::ColorConvertFloat4ToU32(col), + import_clip_preview_); // revealed: no copy + } else { + std::string stars(plen, '*'); // not secret + ImGui::GetWindowDrawList()->AddText(tp, ImGui::ColorConvertFloat4ToU32(col), + stars.c_str()); + } + } + + // Wipe the cache the moment it is no longer shown. + if (!pasteHovered || import_key_input_[0] != '\0') { + if (import_clip_class_ != 0) { + sodium_memzero(import_clip_preview_, sizeof(import_clip_preview_)); + import_clip_class_ = 0; } } diff --git a/src/app.h b/src/app.h index 0bff61b..d7bd762 100644 --- a/src/app.h +++ b/src/app.h @@ -1499,6 +1499,10 @@ private: bool export_in_progress_ = false; // async key fetch running (spinner + disable Export) bool export_error_ = false; // last export returned no key (locked wallet / failure) char import_key_input_[512] = {0}; + char import_clip_preview_[64] = {0}; // trimmed clipboard preview (<=48 chars + NUL); SECRET, wiped + uint8_t import_clip_class_ = 0; // 0 none / 1 ok / 2 wrong-type / 3 unrecognized + double import_clip_next_at_ = 0.0; // ImGui::GetTime() of next allowed clipboard read + bool import_clip_hover_prev_ = false; // hover edge std::string export_address_; std::string import_status_; bool import_success_ = false;