diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp index 17205687a54e..d055bfec89d0 100644 --- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp @@ -496,23 +496,119 @@ class AdvancedPaste : public PowertoyModuleIface if (!GetGUIThreadInfo(0, &gui_info)) { + Logger::warn(L"Auto-copy: GetGUIThreadInfo failed (error={})", GetLastError()); return false; } HWND target = gui_info.hwndFocus ? gui_info.hwndFocus : gui_info.hwndActive; if (!target) { + Logger::warn(L"Auto-copy: no focused or active window found"); return false; } DWORD_PTR result = 0; - return SendMessageTimeout(target, - WM_COPY, - 0, - 0, - SMTO_ABORTIFHUNG | SMTO_BLOCK, - 50, - &result) != 0; + auto sendResult = SendMessageTimeout(target, WM_COPY, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 50, &result); + return sendResult != 0; + } + + // Helper: poll clipboard sequence number for a change from initial_sequence. + // Returns true if the sequence number changed within the given number of polls. + bool poll_clipboard_sequence(DWORD initial_sequence, int poll_attempts, std::chrono::milliseconds poll_delay) + { + for (int poll = 0; poll < poll_attempts; ++poll) + { + if (GetClipboardSequenceNumber() != initial_sequence) + { + return true; + } + std::this_thread::sleep_for(poll_delay); + } + return false; + } + + // Helper: send Ctrl+C via SendInput, releasing any held modifier keys first + // (the hotkey combination may still have modifiers physically pressed). + bool send_ctrl_c_input() + { + std::vector inputs; + + // Release all modifier keys that are currently held down from the hotkey. + // Without this, the target app sees e.g. Win+Shift+Ctrl+C instead of just Ctrl+C. + try_inject_modifier_key_up(inputs, VK_LCONTROL); + try_inject_modifier_key_up(inputs, VK_RCONTROL); + try_inject_modifier_key_up(inputs, VK_LWIN); + try_inject_modifier_key_up(inputs, VK_RWIN); + try_inject_modifier_key_up(inputs, VK_LSHIFT); + try_inject_modifier_key_up(inputs, VK_RSHIFT); + try_inject_modifier_key_up(inputs, VK_LMENU); + try_inject_modifier_key_up(inputs, VK_RMENU); + + // Ctrl down + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = VK_CONTROL; + input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; + inputs.push_back(input_event); + } + + // C down + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = 0x43; // C + input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; + inputs.push_back(input_event); + } + + // C up + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = 0x43; // C + input_event.ki.dwFlags = KEYEVENTF_KEYUP; + input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; + inputs.push_back(input_event); + } + + // Ctrl up + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = VK_CONTROL; + input_event.ki.dwFlags = KEYEVENTF_KEYUP; + input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; + inputs.push_back(input_event); + } + + // Restore modifiers that were held down + try_inject_modifier_key_restore(inputs, VK_LCONTROL); + try_inject_modifier_key_restore(inputs, VK_RCONTROL); + try_inject_modifier_key_restore(inputs, VK_LWIN); + try_inject_modifier_key_restore(inputs, VK_RWIN); + try_inject_modifier_key_restore(inputs, VK_LSHIFT); + try_inject_modifier_key_restore(inputs, VK_RSHIFT); + try_inject_modifier_key_restore(inputs, VK_LMENU); + try_inject_modifier_key_restore(inputs, VK_RMENU); + + // Prevent Start Menu from activating after Win key release/restore + INPUT dummyEvent = {}; + dummyEvent.type = INPUT_KEYBOARD; + dummyEvent.ki.wVk = 0xFF; + dummyEvent.ki.dwFlags = KEYEVENTF_KEYUP; + inputs.push_back(dummyEvent); + + auto uSent = SendInput(static_cast(inputs.size()), inputs.data(), sizeof(INPUT)); + if (uSent != inputs.size()) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L""); + Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput"); + return false; + } + return true; } bool send_copy_selection() @@ -526,78 +622,30 @@ class AdvancedPaste : public PowertoyModuleIface for (int attempt = 0; attempt < copy_attempts; ++attempt) { const auto initial_sequence = GetClipboardSequenceNumber(); - copy_succeeded = try_send_copy_message(); - if (!copy_succeeded) - { - std::vector inputs; + // Strategy 1: Try WM_COPY message (works for standard Win32 controls) + bool wm_copy_sent = try_send_copy_message(); - // send Ctrl+C (key downs and key ups) - { - INPUT input_event = {}; - input_event.type = INPUT_KEYBOARD; - input_event.ki.wVk = VK_CONTROL; - input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; - inputs.push_back(input_event); - } - - { - INPUT input_event = {}; - input_event.type = INPUT_KEYBOARD; - input_event.ki.wVk = 0x43; // C - // Avoid triggering detection by the centralized keyboard hook. - input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; - inputs.push_back(input_event); - } - - { - INPUT input_event = {}; - input_event.type = INPUT_KEYBOARD; - input_event.ki.wVk = 0x43; // C - input_event.ki.dwFlags = KEYEVENTF_KEYUP; - // Avoid triggering detection by the centralized keyboard hook. - input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; - inputs.push_back(input_event); - } - - { - INPUT input_event = {}; - input_event.type = INPUT_KEYBOARD; - input_event.ki.wVk = VK_CONTROL; - input_event.ki.dwFlags = KEYEVENTF_KEYUP; - input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; - inputs.push_back(input_event); - } - - auto uSent = SendInput(static_cast(inputs.size()), inputs.data(), sizeof(INPUT)); - if (uSent != inputs.size()) - { - DWORD errorCode = GetLastError(); - auto errorMessage = get_last_error_message(errorCode); - Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L""); - Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput"); - } - else + if (wm_copy_sent) + { + if (poll_clipboard_sequence(initial_sequence, clipboard_poll_attempts, clipboard_poll_delay)) { copy_succeeded = true; } } - if (copy_succeeded) + // Strategy 2: If WM_COPY didn't work, try SendInput Ctrl+C (works for Electron, browsers, etc.) + if (!copy_succeeded) { - bool sequence_changed = false; - for (int poll_attempt = 0; poll_attempt < clipboard_poll_attempts; ++poll_attempt) + const auto sequence_before_ctrl_c = GetClipboardSequenceNumber(); + + if (send_ctrl_c_input()) { - if (GetClipboardSequenceNumber() != initial_sequence) + if (poll_clipboard_sequence(sequence_before_ctrl_c, clipboard_poll_attempts, clipboard_poll_delay)) { - sequence_changed = true; - break; + copy_succeeded = true; } - - std::this_thread::sleep_for(clipboard_poll_delay); } - - copy_succeeded = sequence_changed; } if (copy_succeeded) @@ -611,6 +659,11 @@ class AdvancedPaste : public PowertoyModuleIface } } + if (!copy_succeeded) + { + Logger::warn(L"Auto-copy: all {} copy attempts failed — the target application did not update the clipboard after WM_COPY and Ctrl+C", copy_attempts); + } + return copy_succeeded; } @@ -977,6 +1030,7 @@ class AdvancedPaste : public PowertoyModuleIface { if (!send_copy_selection()) { + Logger::warn(L"Auto-copy: failed to copy selection for custom action index {} — aborting action", custom_action_index); return false; } }