@@ -496,108 +496,156 @@ class AdvancedPaste : public PowertoyModuleIface
496496
497497 if (!GetGUIThreadInfo (0 , &gui_info))
498498 {
499+ Logger::warn (L" Auto-copy: GetGUIThreadInfo failed (error={})" , GetLastError ());
499500 return false ;
500501 }
501502
502503 HWND target = gui_info.hwndFocus ? gui_info.hwndFocus : gui_info.hwndActive ;
503504 if (!target)
504505 {
506+ Logger::warn (L" Auto-copy: no focused or active window found" );
505507 return false ;
506508 }
507509
508510 DWORD_PTR result = 0 ;
509- return SendMessageTimeout (target,
510- WM_COPY,
511- 0 ,
512- 0 ,
513- SMTO_ABORTIFHUNG | SMTO_BLOCK,
514- 50 ,
515- &result) != 0 ;
511+ auto sendResult = SendMessageTimeout (target, WM_COPY, 0 , 0 , SMTO_ABORTIFHUNG | SMTO_BLOCK, 50 , &result);
512+ return sendResult != 0 ;
513+ }
514+
515+ // Helper: poll clipboard sequence number for a change from initial_sequence.
516+ // Returns true if the sequence number changed within the given number of polls.
517+ bool poll_clipboard_sequence (DWORD initial_sequence, int poll_attempts, std::chrono::milliseconds poll_delay)
518+ {
519+ for (int poll = 0 ; poll < poll_attempts; ++poll)
520+ {
521+ if (GetClipboardSequenceNumber () != initial_sequence)
522+ {
523+ return true ;
524+ }
525+ std::this_thread::sleep_for (poll_delay);
526+ }
527+ return false ;
528+ }
529+
530+ // Helper: send Ctrl+C via SendInput, releasing any held modifier keys first
531+ // (the hotkey combination may still have modifiers physically pressed).
532+ bool send_ctrl_c_input ()
533+ {
534+ std::vector<INPUT> inputs;
535+
536+ // Release all modifier keys that are currently held down from the hotkey.
537+ // Without this, the target app sees e.g. Win+Shift+Ctrl+C instead of just Ctrl+C.
538+ try_inject_modifier_key_up (inputs, VK_LCONTROL);
539+ try_inject_modifier_key_up (inputs, VK_RCONTROL);
540+ try_inject_modifier_key_up (inputs, VK_LWIN);
541+ try_inject_modifier_key_up (inputs, VK_RWIN);
542+ try_inject_modifier_key_up (inputs, VK_LSHIFT);
543+ try_inject_modifier_key_up (inputs, VK_RSHIFT);
544+ try_inject_modifier_key_up (inputs, VK_LMENU);
545+ try_inject_modifier_key_up (inputs, VK_RMENU);
546+
547+ // Ctrl down
548+ {
549+ INPUT input_event = {};
550+ input_event.type = INPUT_KEYBOARD;
551+ input_event.ki .wVk = VK_CONTROL;
552+ input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
553+ inputs.push_back (input_event);
554+ }
555+
556+ // C down
557+ {
558+ INPUT input_event = {};
559+ input_event.type = INPUT_KEYBOARD;
560+ input_event.ki .wVk = 0x43 ; // C
561+ input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
562+ inputs.push_back (input_event);
563+ }
564+
565+ // C up
566+ {
567+ INPUT input_event = {};
568+ input_event.type = INPUT_KEYBOARD;
569+ input_event.ki .wVk = 0x43 ; // C
570+ input_event.ki .dwFlags = KEYEVENTF_KEYUP;
571+ input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
572+ inputs.push_back (input_event);
573+ }
574+
575+ // Ctrl up
576+ {
577+ INPUT input_event = {};
578+ input_event.type = INPUT_KEYBOARD;
579+ input_event.ki .wVk = VK_CONTROL;
580+ input_event.ki .dwFlags = KEYEVENTF_KEYUP;
581+ input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
582+ inputs.push_back (input_event);
583+ }
584+
585+ // Restore modifiers that were held down
586+ try_inject_modifier_key_restore (inputs, VK_LCONTROL);
587+ try_inject_modifier_key_restore (inputs, VK_RCONTROL);
588+ try_inject_modifier_key_restore (inputs, VK_LWIN);
589+ try_inject_modifier_key_restore (inputs, VK_RWIN);
590+ try_inject_modifier_key_restore (inputs, VK_LSHIFT);
591+ try_inject_modifier_key_restore (inputs, VK_RSHIFT);
592+ try_inject_modifier_key_restore (inputs, VK_LMENU);
593+ try_inject_modifier_key_restore (inputs, VK_RMENU);
594+
595+ // Prevent Start Menu from activating after Win key release/restore
596+ INPUT dummyEvent = {};
597+ dummyEvent.type = INPUT_KEYBOARD;
598+ dummyEvent.ki .wVk = 0xFF ;
599+ dummyEvent.ki .dwFlags = KEYEVENTF_KEYUP;
600+ inputs.push_back (dummyEvent);
601+
602+ auto uSent = SendInput (static_cast <UINT>(inputs.size ()), inputs.data (), sizeof (INPUT));
603+ if (uSent != inputs.size ())
604+ {
605+ DWORD errorCode = GetLastError ();
606+ auto errorMessage = get_last_error_message (errorCode);
607+ Logger::error (L" SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}" , inputs.size (), uSent, errorMessage.has_value () ? errorMessage.value () : L" " );
608+ Trace::AdvancedPaste_Error (errorCode, errorMessage.has_value () ? errorMessage.value () : L" " , L" input.SendInput" );
609+ return false ;
610+ }
611+ return true ;
516612 }
517613
518614 bool send_copy_selection ()
519615 {
520616 constexpr int copy_attempts = 2 ;
521- constexpr auto copy_retry_delay = std::chrono::milliseconds (100 );
522- constexpr int clipboard_poll_attempts = 5 ;
523- constexpr auto clipboard_poll_delay = std::chrono::milliseconds (30 );
617+ constexpr auto copy_retry_delay = std::chrono::milliseconds (150 );
618+ constexpr int clipboard_poll_attempts = 10 ;
619+ constexpr auto clipboard_poll_delay = std::chrono::milliseconds (50 );
524620
525621 bool copy_succeeded = false ;
526622 for (int attempt = 0 ; attempt < copy_attempts; ++attempt)
527623 {
528624 const auto initial_sequence = GetClipboardSequenceNumber ();
529- copy_succeeded = try_send_copy_message ();
530625
531- if (!copy_succeeded)
532- {
533- std::vector<INPUT> inputs;
626+ // Strategy 1: Try WM_COPY message (works for standard Win32 controls)
627+ bool wm_copy_sent = try_send_copy_message ();
534628
535- // send Ctrl+C (key downs and key ups)
536- {
537- INPUT input_event = {};
538- input_event.type = INPUT_KEYBOARD;
539- input_event.ki .wVk = VK_CONTROL;
540- input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
541- inputs.push_back (input_event);
542- }
543-
544- {
545- INPUT input_event = {};
546- input_event.type = INPUT_KEYBOARD;
547- input_event.ki .wVk = 0x43 ; // C
548- // Avoid triggering detection by the centralized keyboard hook.
549- input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
550- inputs.push_back (input_event);
551- }
552-
553- {
554- INPUT input_event = {};
555- input_event.type = INPUT_KEYBOARD;
556- input_event.ki .wVk = 0x43 ; // C
557- input_event.ki .dwFlags = KEYEVENTF_KEYUP;
558- // Avoid triggering detection by the centralized keyboard hook.
559- input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
560- inputs.push_back (input_event);
561- }
562-
563- {
564- INPUT input_event = {};
565- input_event.type = INPUT_KEYBOARD;
566- input_event.ki .wVk = VK_CONTROL;
567- input_event.ki .dwFlags = KEYEVENTF_KEYUP;
568- input_event.ki .dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
569- inputs.push_back (input_event);
570- }
571-
572- auto uSent = SendInput (static_cast <UINT>(inputs.size ()), inputs.data (), sizeof (INPUT));
573- if (uSent != inputs.size ())
574- {
575- DWORD errorCode = GetLastError ();
576- auto errorMessage = get_last_error_message (errorCode);
577- Logger::error (L" SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}" , inputs.size (), uSent, errorMessage.has_value () ? errorMessage.value () : L" " );
578- Trace::AdvancedPaste_Error (errorCode, errorMessage.has_value () ? errorMessage.value () : L" " , L" input.SendInput" );
579- }
580- else
629+ if (wm_copy_sent)
630+ {
631+ if (poll_clipboard_sequence (initial_sequence, clipboard_poll_attempts, clipboard_poll_delay))
581632 {
582633 copy_succeeded = true ;
583634 }
584635 }
585636
586- if (copy_succeeded)
637+ // Strategy 2: If WM_COPY didn't work, try SendInput Ctrl+C (works for Electron, browsers, etc.)
638+ if (!copy_succeeded)
587639 {
588- bool sequence_changed = false ;
589- for (int poll_attempt = 0 ; poll_attempt < clipboard_poll_attempts; ++poll_attempt)
640+ const auto sequence_before_ctrl_c = GetClipboardSequenceNumber ();
641+
642+ if (send_ctrl_c_input ())
590643 {
591- if (GetClipboardSequenceNumber () != initial_sequence )
644+ if (poll_clipboard_sequence (sequence_before_ctrl_c, clipboard_poll_attempts, clipboard_poll_delay) )
592645 {
593- sequence_changed = true ;
594- break ;
646+ copy_succeeded = true ;
595647 }
596-
597- std::this_thread::sleep_for (clipboard_poll_delay);
598648 }
599-
600- copy_succeeded = sequence_changed;
601649 }
602650
603651 if (copy_succeeded)
@@ -611,6 +659,11 @@ class AdvancedPaste : public PowertoyModuleIface
611659 }
612660 }
613661
662+ if (!copy_succeeded)
663+ {
664+ 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);
665+ }
666+
614667 return copy_succeeded;
615668 }
616669
@@ -977,6 +1030,7 @@ class AdvancedPaste : public PowertoyModuleIface
9771030 {
9781031 if (!send_copy_selection ())
9791032 {
1033+ Logger::warn (L" Auto-copy: failed to copy selection for custom action index {} — aborting action" , custom_action_index);
9801034 return false ;
9811035 }
9821036 }
0 commit comments