Skip to content

Commands calling /*window*/ copy() fail with ‘Failed to copy to clipboard!’ error when modifier key held for too long #3428

Closed

Description

@Chealer

Describe the bug
When a command calls /*window*/ copy(), CopyQ uses gymnastics to attempt copying, as documented in ticket #3395. The heuristic it uses is to simulate a shortcut likely to cause a copy.

Presumably, that is why sendKeyPress() waits for modifier keys to be released before simulating, at least on Microsoft Windows and X11. I suppose failing to do so could fail to achieve the wanted effect, or even make an undesired effect more likely.

However, this delays the command. To avoid blocking for too long, waitForModifiersReleased() only waits up to Config::window_wait_for_modifier_released_ms milliseconds, which is 2 s by default. If a modifier is still pressed after that, it returns false, which interrupts sendKeyPress() before anything is simulated. /*window*/ copy()’s heuristic check should then throw its ‘Failed to copy to clipboard!’ exception, noticing that the clipboard remained unchanged.

To Reproduce

  1. Select some text, for example in this window
  2. Push Ctrl down
  3. Launch a command calling /*window*/ copy(), for example via the system tray menu
  4. Wait for 2 s

Simply calling the dummy command which beefeater7 provides in the body of ticket #1440 while holding any modifier should suffice to reproduce:

  1. Select some text, for example in this window
  2. Push Shift down
  3. Press F4
  4. Wait for 2 s

Expected behavior
The command succeeds, copying the selected text.

Actual behavior
The command fails, throwing a ‘Failed to copy to clipboard!’ exception.

Version, OS and Environment
CopyQ Clipboard Manager 13.0.0-101-gb8b0493e
Qt: 6.8.3
KNotifications: 6.15.0
Compiler: MSVC
Arch: x86_64-little_endian-llp64
OS: Windows 11 Version 25H2

  • Windows installer

CopyQ 17.0.0
Arch: x86_64-little_endian-llp64
Compiler: MSVC
KNotifications: 6.29.0
KStatusNotifierItem: 6.29.0
OS: Windows 11 Version 26H2
Qt: 6.10.3
has-global-shortcuts: 1

🅭🄍

Activity

  1. hluk commented on Feb 12, 2026

    @hluk
    Owner

    Not sure if I understand the issue correctly, but copy() and paste() are expected to fail if the user still holds the modifier keys - in such case, CopyQ cannot send the correct shortcut to the target window.

    In old versions, CopyQ force-released the modifiers held by user (6ca729a) - but that did not seem as a good solution.

  2. Chealer commented on Feb 12, 2026

    @Chealer
    ContributorAuthor

    Ah, it is true that paste() also fails (although quietly). Thank you, I guess this could be considered part of the same issue.

    I am not aware of a way to fix this on Windows, so I think the first step could be to better document, and fail better. The following rough patch / pseudocode most likely doesn't even build, but demonstrates how the error can be clarified:

    diff --git a/src/platform/win/winplatformwindow.cpp b/src/platform/win/winplatformwindow.cpp
    --- src/platform/win/winplatformwindow.cpp
    +++ src/platform/win/winplatformwindow.cpp
    @@ -10,8 +10,9 @@
     #include <QApplication>
     #include <QElapsedTimer>
     #include <QString>
     #include <QVector>
    +#include <stdexcept>
     
     namespace {
     
     QString windowTitle(HWND window)
    @@ -168,12 +169,22 @@
     void WinPlatformWindow::copy()
     {
         const AppConfig config;
     
    +    /* Windows offers no proper API for copying; instead:
    +        check which item is last in the clipboard
    +        try copying by simulating shortcut Ctrl+C
    +        then block until we detect a newer item. */
    +
         const DWORD clipboardSequenceNumber = GetClipboardSequenceNumber();
    -    sendKeyPress(VK_LCONTROL, 'C', config);
    +    
    +    try {
    +        sendKeyPress(VK_LCONTROL, 'C', config);
    +    } catch (const std::runtime_error& e) {
    +        std::throw_with_nested(std::runtime_error("Copying selection failed"));
    +    }
     
    -    // Wait for clipboard to change.
    +    // Try to wait until the clipboard has changed, but stop blocking if it still hasn't after 2 s.
         QElapsedTimer t;
         t.start();
         while ( clipboardSequenceNumber == GetClipboardSequenceNumber() && t.elapsed() < 2000 )
             QApplication::processEvents(QEventLoop::AllEvents, 100);
    @@ -187,11 +198,12 @@
             return;
     
         waitMs(config.option<Config::window_wait_after_raised_ms>());
     
    -    // Wait for user to release modifiers.
    +    // Wait for user to release modifiers, to prevent the shortcut from being misinterpreted (see ticket #3428)
    +    // TODO: Ignore modifiers part of the sent shortcut
         if (!waitForModifiersReleased(config))
    -        return;
    +        throw std::runtime_error("Keyboard shortcut simulation timed out due to a key modifier");
     
         const int keyPressTimeMs = config.option<Config::window_key_press_time_ms>();
         if (keyPressTimeMs <= 0) {
             sendInputs({
    
    diff --git a/src/scriptable/scriptable.cpp b/src/scriptable/scriptable.cpp
    --- src/scriptable/scriptable.cpp
    +++ src/scriptable/scriptable.cpp
    @@ -66,8 +66,10 @@
     #ifdef WITH_NATIVE_NOTIFICATIONS
     #   include <knotifications_version.h>
     #endif
     
    +#include <stdexcept>
    +
     Q_DECLARE_METATYPE(QByteArray*)
     Q_DECLARE_METATYPE(QFile*)
     
     namespace {
    @@ -3018,9 +3020,13 @@
             const QByteArray value = "invalid";
             const QVariantMap data = createDataMap(mime, value);
             m_proxy->setClipboard(data, mode);
     
    -        m_proxy->copyFromCurrentWindow();
    +        try {
    +            m_proxy->copyFromCurrentWindow();
    +        } catch (const std::runtime_error& e) {
    +            return throwError( tr("Failed to copy to clipboard!") << " " << e.what();
    +        }
     
             // Wait for clipboard to be set.
             for (int i = 0; i < 10; ++i) {
                 if ( getClipboardData(mime) != value )
    
    

    I did not adapt other callers to handle such exceptions.

    There is already a section warning about /*window*/ copy(). Widening it to cover paste() and pointing to relevant tickets (at least this one and #3395) would clarify a lot.

  3. hluk commented on Feb 13, 2026

    @hluk
    Owner

    Thanks for clarification. Let me try to improve the error handling for paste/copy operations.

  4. added a commit that references this issue on Feb 13, 2026
    809c660
  5. hluk commented on Feb 13, 2026

    @hluk
    Owner

    Improvements in #3429.

    I also noticed that I confused the copy/paste shortcuts and the option in the API docs for copy(). 🤦 That's also fixed in the PR.

  6. added a commit that references this issue on Feb 13, 2026
    9e76507
  7. Chealer commented on Feb 13, 2026

    @Chealer
    ContributorAuthor

    Thank you very much Lukas, that makes a lot more sense.
    Also, you understood very well the spirit of the patch, and your proposal brings it much further.
    I rephrased the error message and made further cleanups (clarifications and simplifications) and fixes in the following:

    diff --git a/docs/known-issues.rst b/docs/known-issues.rst
    --- docs/known-issues.rst
    +++ docs/known-issues.rst
    @@ -110,16 +110,17 @@
         for details on how to add the command).
     
         `Issue #27 <https://github.com/hluk/CopyQ/issues/27>`__
     
    -Scripting command "copy()" fails
    +Scripting function "copy()" fails
     --------------------------------
     
    -The command ``copy()`` sends the Ctrl+C shortcut to the current window.
    -This can fail depending on the active application.
    -If CopyQ won't detect a clipboard change, it throws an exception.
    -The execution then fails with the message ``Failed to copy to clipboard!``.
    +The ``copy()`` function simulates a shortcut (usually :kbd:`Control+C`) to the focused target (generally, the current window).
    +This heuristic can fail, depending on the context (target / active application) and which modifier keys are held.
     
    +CopyQ tries to detect failure by checking if the clipboard changes. If it did not after a predefined duration, it assumes there was a failure and throws an exception.
    +The calling command then fails with the message ``Failed to copy to clipboard!``.
    +
     An alternative under Windows is to use a Powershell script to override the ``copy`` operation
     (see :ref:`faq-share-commands`):
     
     .. code-block:: powershell
    @@ -137,4 +138,8 @@
         IsScript=true
         Name=Override copy()
     
     The delays are added to make sure no focus issues occur and the text is copied to the clipboard.
    +
    +.. seealso::
    +
    +    - `Issue #3395 <https://github.com/hluk/CopyQ/issues/3395>`__
    
    diff --git a/docs/scripting-api.rst b/docs/scripting-api.rst
    --- docs/scripting-api.rst
    +++ docs/scripting-api.rst
    @@ -336,16 +336,16 @@
            copy(item)
     
     .. js:function:: /*window*/ copy()
     
    -   Sends ``Ctrl+C`` to the current application or window to trigger a copy
    +   Sends :kbd:`Ctrl+C` or :kbd:`Cmd+C` to the target (current application or window) to trigger a copy
        operation.
     
        The function resets the clipboard before sending the shortcut so it can
        detect changes properly.
     
        :throws Error: Thrown if the clipboard does not change. This can indicate
    -      that sending the shortcut failed, the target current window could not be
    +      that sending the shortcut failed, that the target (current window) could not be
           retrieved, or that nothing valid was selected to copy. In such case, see
           logs for more details.
     
        Example:
    @@ -383,12 +383,12 @@
        :throws Error: Thrown if selection fails to be set.
     
     .. js:function:: paste()
     
    -   Sends ``Shift+Insert`` (or ``Ctrl+V``) to the current application or window
    -   to trigger a paste operation.
    +   Attempts to trigger a paste operation.
    +   To try achieving this, sends to the target (current application or window) a keyboard shortcut, either :kbd:`Shift+Insert`, :kbd:`Cmd+V` or :kbd:`Ctrl+V`.
     
    -   If the regular expression in the option ``window_paste_with_ctrl_v_regex``
    +   On Microsoft Windows and X11, if the regular expression in the option ``window_paste_with_ctrl_v_regex``
        matches the current window title, the shortcut ``Ctrl+V`` is sent instead of
        the default ``Shift+Insert``. For example, to override the option so that
        ``Ctrl+V`` is sent for all windows, call:
     
    @@ -399,9 +399,9 @@
        Correct functionality depends a lot on target application and window
        manager.
     
        :throws Error: Thrown if paste operation fails. This can indicate that
    -      sending the shortcut failed, the target current window could not be
    +      simulating the shortcut failed, or that the target (current window) could not be
           retrieved. In such case, see logs for more details.
     
        Example:
     
    diff --git a/src/platform/win/winplatformwindow.cpp b/src/platform/win/winplatformwindow.cpp
    --- src/platform/win/winplatformwindow.cpp
    +++ src/platform/win/winplatformwindow.cpp
    @@ -175,9 +175,9 @@
         const DWORD clipboardSequenceNumber = GetClipboardSequenceNumber();
         if ( !sendKeyPress(VK_LCONTROL, 'C', config) )
             return false;
     
    -    // Wait for clipboard to change.
    +    // Check whether it worked; try to wait until the clipboard has changed, but stop blocking if it still hasn't after 2 s.
         QElapsedTimer t;
         t.start();
         while ( clipboardSequenceNumber == GetClipboardSequenceNumber() && t.elapsed() < 2000 )
             QApplication::processEvents(QEventLoop::AllEvents, 100);
    @@ -193,11 +193,12 @@
             return false;
     
         waitMs(config.option<Config::window_wait_after_raised_ms>());
     
    -    // Wait for user to release modifiers.
    +    // Check that―or wait until―user is not pressing modifiers, to prevent the shortcut from being misinterpreted (see ticket #3428)
    +    // TODO: Ignore modifiers part of the sent shortcut
         if (!waitForModifiersReleased(config)) {
    -        logWindowWarning("Failed to simulate key presses while modifiers are pressed", m_window);
    +        logWindowWarning("Failed to simulate keyboard shortcut; waiting for modifiers to be released timed out", m_window);
             return false;
         }
     
         const int keyPressTimeMs = config.option<Config::window_key_press_time_ms>();
    
    diff --git a/src/platform/win/winplatformwindow.h b/src/platform/win/winplatformwindow.h
    --- src/platform/win/winplatformwindow.h
    +++ src/platform/win/winplatformwindow.h
    @@ -24,8 +24,11 @@
         bool pasteFromClipboard() override;
         bool copyToClipboard() override;
     
     private:
    +    /**
    +     * Simulate a keyboard shortcut consisting of 1 modifier and 1 non-modifier key
    +     */
         bool sendKeyPress(WORD modifier, WORD key, const AppConfig &config);
     
         HWND m_window;
     };
    
    diff --git a/src/platform/x11/x11platformwindow.cpp b/src/platform/x11/x11platformwindow.cpp
    --- src/platform/x11/x11platformwindow.cpp
    +++ src/platform/x11/x11platformwindow.cpp
    @@ -43,9 +43,9 @@
     #ifdef HAS_X11TEST
     bool fakeKeyEvent(Display* display, unsigned int keyCode, Bool isPress, unsigned long delayMs = CurrentTime)
     {
         if (!XTestFakeKeyEvent(display, keyCode, isPress, delayMs)) {
    -        log( QStringLiteral("Failed to send key event (key code: %1, isPress: %2)")
    +        log( QStringLiteral("Failed to simulate key event (key code: %1, isPress: %2)")
                    .arg(keyCode)
                    .arg(isPress), LogWarning );
             return false;
         }
    @@ -104,37 +104,40 @@
     }
     
     bool simulateKeyPress(Display *display, const QList<int> &modCodes, unsigned int key, const AppConfig &config)
     {
    -    // Wait for user to release modifiers.
    +    // Check that―or wait until―user is not pressing modifiers, to prevent the shortcut from being misinterpreted (see ticket #3428)
    +    // TODO: Ignore modifiers part of the simulated shortcut
         if (!waitForModifiersReleased(display, config)) {
    -        log("Failed to simulate key presses while modifiers are pressed", LogWarning);
    +        log("Failed to simulate keyboard shortcut; waiting for modifiers to be released timed out", LogWarning);
             return false;
         }
     
    -    if ( !simulateModifierKeyPress(display, modCodes, True) )
    -        return false;
    -
    +    
         const KeyCode keyCode = XKeysymToKeycode(display, key);
         if (keyCode == 0) {
             log( QStringLiteral("Failed to get X11 key code for key symbol: %1")
    -                   .arg(key), LogWarning );
    -        simulateModifierKeyPress(display, modCodes, False);
    +        .arg(key), LogWarning );
    +    }
    +    
    +    // Simulate depression of the modifiers
    +    if ( !simulateModifierKeyPress(display, modCodes, True) )
             return false;
    -    }
     
    +    // Simulate depression of the key
         if ( !fakeKeyEvent(display, keyCode, True) ) {
             simulateModifierKeyPress(display, modCodes, False);
             return false;
         }
     
    -    // This is needed to paste into URL bar in Chrome.
    +    // Simulate release of the key (needed to paste into URL bar in Chrome)
         const unsigned long delayMs = config.option<Config::window_key_press_time_ms>();
         if ( !fakeKeyEvent(display, keyCode, False, delayMs) ) {
             simulateModifierKeyPress(display, modCodes, False);
             return false;
         }
     
    +    // Release the modifiers
         if ( !simulateModifierKeyPress(display, modCodes, False) )
             return false;
     
         XSync(display, False);
    
    diff --git a/src/platform/x11/x11platformwindow.h b/src/platform/x11/x11platformwindow.h
    --- src/platform/x11/x11platformwindow.h
    +++ src/platform/x11/x11platformwindow.h
    @@ -27,8 +27,11 @@
     
     private:
         bool waitForFocus(int ms);
     
    +    /**
    +     * Simulate a keyboard shortcut consisting of 1 modifier and 1 non-modifier key
    +     */
         bool sendKeyPress(int modifier, int key, const AppConfig &config);
     
         quintptr m_window;
     };
    
  8. hluk commented on Feb 14, 2026

    @hluk
    Owner

    Instead of posting diffs in comments, please create pull requests, or add suggestions in an existing open pull request.

  9. added 2 commits that reference this issue on Feb 14, 2026
    37ffb84
    c0d71c1
  10. added a commit that references this issue on Feb 14, 2026
    3ac7fb3
  11. Chealer commented on Feb 14, 2026

    @Chealer
    ContributorAuthor

    Assuming you are talking about merge requests, I don't have any branch to merge. I don’t even have a commit; I just quickly crafted that patch―which is entirely untested―using github.dev.
    Is there an "existing open pull request" where you think that patch could go?

  12. hluk commented on Feb 16, 2026

    @hluk
    Owner

    Is there an "existing open pull request" where you think that patch could go?

    In this case, just comment on specific line in the PR: https://github.com/hluk/CopyQ/pull/3429/changes

    But I already closed the PR. If you create a PR yourself, GitHub Actions and other CI tasks should ensure that any changes are valid.

  13. Chealer commented on Feb 16, 2026

    @Chealer
    ContributorAuthor

    Hum, I struggle to understand… you are saying it is too late now?
    If not, I know how to add actual comments in that page, but I would not know what to do with a patch. You are suggesting to put it in the Conversation tab or in the Files changed tab?
    I am not familiar with GitHub “pull requests”; are there some examples with patches?

  14. hluk commented on Feb 17, 2026

    @hluk
    Owner

    Hum, I struggle to understand… you are saying it is too late now?

    I can apply comments with "code suggestions" (or whatever github calls it) easily while the pull request (PR) is open. But it is closed now, so someone would need to create a new PR.

    If not, I know how to add actual comments in that page, but I would not know what to do with a patch. You are suggesting to put it in the Conversation tab or in the Files changed tab?

    I meant, you ideally add a comment for a specific line in the diff:

    Image

    Or range of lines by holding and dragging mouse button:

    Image

    For the changed lines, you can add a suggestion by modifying the cited line of code in the comment:

    Image

    I am not familiar with GitHub “pull requests”; are there some examples with patches?

    I think perhaps the easiest way to start is to fork this repo (button at https://github.com/hluk/CopyQ) and start editing directly on GitHub - a single file (I think saving asks if you want to create PR) or in Codespaces (I do not use it, but I think it allows editing multiple files and managing the changes in separate git branch from which you could create PR).

  15. Chealer commented on Feb 17, 2026

    @Chealer
    ContributorAuthor

    […]

    For the changed lines, you can add a suggestion by modifying the cited line of code in the comment:

    Ooh, I see. That is not as convenient as github.dev, which offers considerable assistance (like syntax highlighting) but it is great for any trivial change; thank you very much Lukas, I will certainly consider that in the future.

    I think perhaps the easiest way to start is to fork this repo (button at https://github.com/hluk/CopyQ) and start editing directly on GitHub - a single file (I think saving asks if you want to create PR) or in Codespaces (I do not use it, but I think it allows editing multiple files and managing the changes in separate git branch from which you could create PR).

    Hum, so I would need to create some kind of soft fork?🤔 Is the process documented somewhere?
    Almost all of my implementation experience is from industrial contexts, so I know (too) many tools, but all are (semi-)professional IDEs, like IntelliJ or Eclipse, so I have never used Codespaces or anything like that on GitHub before creating the above patches. If one of your options allows converting the patch into what you suggest, let me know.

  16. hluk commented on Feb 19, 2026

    @hluk
    Owner

    Hum, so I would need to create some kind of soft fork?🤔 Is the process documented somewhere?

    GitHub Docs - Fork a repository - I think you can ignore the "cloning" part and just use Codespaces

    GitHub Docs - Quickstart for GitHub Codespaces - it is basically a browser version of Visual Studio Code

    Now I wonder if there is an alternate way which involves simply asking Copilot to create a PR based on a comment (or a diff).

  17. Chealer commented on Feb 19, 2026

    @Chealer
    ContributorAuthor

    Hum, so I would need to create some kind of soft fork?🤔 Is the process documented somewhere?

    GitHub Docs - Fork a repository - I think you can ignore the "cloning" part and just use Codespaces

    GitHub Docs - Quickstart for GitHub Codespaces - it is basically a browser version of Visual Studio Code

    Thanks, but my question referred to the process to change the code you suggest would be better than submitting patches via comments.

    To clarify my question about forks, I am still evaluating whether I will switch to CopyQ, and certainly have no intention of maintaining a fork even if I did abandon my current manager, so forking this repository doesn't sound right. Were you suggesting some kind of dummy fork, which might disappear an hour after creation, or what?

    Now I wonder if there is an alternate way which involves simply asking Copilot to create a PR based on a comment (or a diff).

    I do not even know what you mean by "a PR", unless you just mean a merge request? In any case, I have no idea.

  18. pbodnar commented on Feb 21, 2026

    @pbodnar
    Contributor

    Hi @Chealer, if I may, here are a few tips:

    1. If you put a diff in a comment, marking it as "diff" helps readability when looking at it in the browser. Example:
    diff --git a/src/platform/win/winplatformwindow.cpp b/src/platform/win/winplatformwindow.cpp
    --- src/platform/win/winplatformwindow.cpp
    +++ src/platform/win/winplatformwindow.cpp
    @@ -10,8 +10,9 @@
     #include <QApplication>
     #include <QElapsedTimer>
     #include <QString>
     #include <QVector>
    +#include <stdexcept>
     
     namespace {
     
     QString windowTitle(HWND window)
    1. PR = pull request (term used by GitHub) = MR = merge request (term used by GitLab)

    2. The "PR way" of contributing seems to be the "industry tested" way and is generally preferred (even though the GitHub's UI for PRs is notoriously known to be far from ideal for bigger PRs).

    One of the biggest advantages is the ability to have controlled discussions over selected lines in the changed files, as illustrated in screenshots by @hluk above.

    Also, this process is quite thoroughly documented, you may also ask you favorite chatbot.

    And no, you don't need to "maintain a fork" - you can delete or re-create the fork ("the forked repository") at anytime if you consider it is no longer needed, e.g. after your PR is successfully merged/pulled to the original repository.

  19. Chealer commented on Feb 27, 2026

    @Chealer
    ContributorAuthor

    Greetings Petr,

    Hi @Chealer, if I may,

    You are most welcome to.

    here are a few tips:

    1. If you put a diff in a comment, marking it as "diff" helps readability when looking at it in the browser.

    Oh, that is great, thank you very much!

    1. The "PR way" of contributing seems to be the "industry tested" way and is generally preferred (even though the GitHub's UI for PRs is notoriously known to be far from ideal for bigger PRs).

    One of the biggest advantages is the ability to have controlled discussions over selected lines in the changed files, as illustrated in screenshots by @hluk above.

    Right, but that is just a feature offered with diffs. You can do the same on any commit.

    In the industry, we just commit changes, unless there is a good reason to branch.

    Also, this process is quite thoroughly documented, you may also ask you favorite chatbot.

    Hum, searching Google for "PR way" seems to find nothing but results about the acronym’s usual sense.

    And no, you don't need to "maintain a fork" - you can delete or re-create the fork ("the forked repository") at anytime if you consider it is no longer needed, e.g. after your PR is successfully merged/pulled to the original repository.

    OK. So if I understand correctly, what Lukas is suggesting is to create some soft fork, perform changes there and to propose merging it right after. If that's documented somewhere, I don't mind trying that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions