You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commands calling /*window*/ copy() fail with ‘Failed to copy to clipboard!’ error when modifier key held for too long #3428
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
Select some text, for example in this window
Push Ctrl down
Launch a command calling /*window*/ copy(), for example via the system tray menu
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:
Select some text, for example in this window
Push Shift down
Press F4
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
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.
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.
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;
};
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?
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?
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:
Or range of lines by holding and dragging mouse button:
For the changed lines, you can add a suggestion by modifying the cited line of code in the comment:
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).
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.
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.
PR = pull request (term used by GitHub) = MR = merge request (term used by GitLab)
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.
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!
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.
Activity
hluk commentedon Feb 12, 2026
Not sure if I understand the issue correctly, but
copy()andpaste()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.
Chealer commentedon Feb 12, 2026
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:
I did not adapt other callers to handle such exceptions.
There is already a section warning about
/*window*/ copy(). Widening it to coverpaste()and pointing to relevant tickets (at least this one and #3395) would clarify a lot.hluk commentedon Feb 13, 2026
Thanks for clarification. Let me try to improve the error handling for paste/copy operations.
Improve copy/paste error handling and fix API documentation
hluk commentedon Feb 13, 2026
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.Improve copy/paste error handling and fix API documentation
Chealer commentedon Feb 13, 2026
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:
hluk commentedon Feb 14, 2026
Instead of posting diffs in comments, please create pull requests, or add suggestions in an existing open pull request.
Improve copy/paste error handling and fix API documentation
Improve copy/paste error handling and fix API documentation
Improve copy/paste error handling and fix API documentation
Chealer commentedon Feb 14, 2026
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?
hluk commentedon Feb 16, 2026
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.
Chealer commentedon Feb 16, 2026
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?
hluk commentedon Feb 17, 2026
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.
I meant, you ideally add a comment for a specific line in the diff:
Or range of lines by holding and dragging mouse button:
For the changed lines, you can add a suggestion by modifying the cited line of code in the comment:
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).
Chealer commentedon Feb 17, 2026
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.
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.
hluk commentedon Feb 19, 2026
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).
Chealer commentedon Feb 19, 2026
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?
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.
pbodnar commentedon Feb 21, 2026
Hi @Chealer, if I may, here are a few tips:
PR = pull request (term used by GitHub) = MR = merge request (term used by GitLab)
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.
Chealer commentedon Feb 27, 2026
Greetings Petr,
You are most welcome to.
Oh, that is great, thank you very much!
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.
Hum, searching Google for "PR way" seems to find nothing but results about the acronym’s usual sense.
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.