{"id":111162,"date":"2025-05-08T07:00:00","date_gmt":"2025-05-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111162"},"modified":"2025-05-08T09:01:59","modified_gmt":"2025-05-08T16:01:59","slug":"20250508-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250508-00\/?p=111162","title":{"rendered":"Why doesn&#8217;t Clipboard History capture rapid changes to clipboard contents?"},"content":{"rendered":"<p>A customer was trying to write a tool to preload items into the clipboard history. In a way, this is sort of the opposite of history. Instead of looking backward to things that were once on the clipboard in the past, they want to look forward into the future and preload things that they anticipate you are soon going to wish were on the clipboard. Here&#8217;s a stripped-down version.<\/p>\n<pre>\/\/ All error checking elided for expository purposes\r\n#include &lt;windows.h&gt;\r\n\r\nvoid SetClipboardText(HWND hwnd, PCWSTR text)\r\n{\r\n    OpenClipboard(hwnd);\r\n    EmptyClipboard();\r\n    auto size = sizeof(wchar_t) * (1 + wcslen(text));\r\n    auto clipData = GlobalAlloc(GMEM_MOVEABLE, size);\r\n    auto buffer = (LPWSTR)GlobalLock(clipData);\r\n    strcpy_s(buffer, size, text);\r\n    GlobalUnlock(clipData);\r\n    SetClipboardData(CF_UNICODETEXT, clipData);\r\n    CloseClipboard();\r\n}\r\n\r\n\/\/ Put these strings in the clipboard history for quick access.\r\nstatic constexpr PCWSTR messages[] = {\r\n    L\"314159\", \/\/ the bug number we want to edit\r\n    L\"e83c5163316f89bfbde7d9ab23ca2e25604af290\", \/\/ the commit to link the bug to\r\n    L\"Widget polarity was set incorrectly.\", \/\/ the comment to add\r\n};\r\n\r\nint wmain([[maybe_unused]] int argc,\r\n          [[maybe_unused]] wchar_t* argv[])\r\n{\r\n    auto tempWindow = CreateWindowExW(0, L\"static\", nullptr, WS_POPUPWINDOW,\r\n            0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);\r\n\r\n    for (auto message : messages)\r\n    {\r\n        SetClipboardText(tempWindow, message);\r\n    }\r\n    DestroyWindow(tempWindow);\r\n    return 0;\r\n}\r\n<\/pre>\n<p>This program sets three strings onto the clipboard one after the other. But when you run it, only the last string makes it into the clipboard history. What happened to the other two?<\/p>\n<p>The clipboard history service operates asynchronously. It registers for clipboard changes via <code>Add\u00adClipboard\u00adFormat\u00adListener<\/code>, and when it receives a change notification, it updates the clipboard history. The listener is notified asynchronous, however, so by the time the listener receives the <code>WM_<wbr \/>CLIPBOARD\u00adUPDATE<\/code> message, the clipboard may have changed a second time.<\/p>\n<p><a title=\"The clipboard viewer linked list is no longer the responsibility of applications to maintain, unless they want to\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110919-00\/?p=9613\"> This is different from clipboard viewers<\/a>, which are notified synchronously when the clipboard changes. The downside is that you might miss out on clipboard changes. The much better upside is that you don&#8217;t slow down or hang the clipboard.<\/p>\n<p>In practice, missing every little clipboard change is sort of a feature of the clipboard history service. I can imagine that there are programs that just spam the clipboard with a rapid sequence of clipboard changes. All of the intermediate ones are useless because they are never on the clipboard long enough for the user to paste them. Only the last one really counts from an end-user point of view, so it&#8217;s reasonable that the clipboard history service matches what the user sees on the clipboard.<\/p>\n<p>Next time, we&#8217;ll see what we can do to repair this program.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Clipboard history operates asynchronously, so you are changing it before it can respond to the changes.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25,104],"class_list":["post-111162","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code","tag-tipssupport"],"acf":[],"blog_post_summary":"<p>Clipboard history operates asynchronously, so you are changing it before it can respond to the changes.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111162","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111162"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111162\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111162"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}