{"id":106731,"date":"2022-06-09T07:00:00","date_gmt":"2022-06-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106731"},"modified":"2024-12-09T19:09:59","modified_gmt":"2024-12-10T03:09:59","slug":"20220609-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220609-00\/?p=106731","title":{"rendered":"How can I wait more than 30 seconds for a delay-rendered clipboard format to become rendered?"},"content":{"rendered":"<p>Last time, we saw that <a title=\"Is there a maximum size for Windows clipboard data? Because I'm getting null for something I know should be there\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220608-00\/?p=106727\"> the system will wait up to 30 seconds for a clipboard owner to produce delay-rendered data<\/a>. But what if you want to extend this timeout?<\/p>\n<p>The timeout itself is embedded in the clipboard manager and is not configurable. Thirty seconds is the longest the clipboard manager will wait for the clipboard data to be rendered.<\/p>\n<p>But that doesn&#8217;t mean that&#8217;s the longest <i>you<\/i> will wait.<\/p>\n<p>Recall that <a title=\"What is the proper handling of WM_RENDERFORMAT and WM_RENDERALLFORMATS?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20121224-00\/?p=5763\"> the <code>WM_<wbr \/>RENDER\u00adFORMAT<\/code> message handler will call <code>Set\u00adClipboard\u00adData<\/code> when it&#8217;s done<\/a>, hoping that the clipboard is still open by the original code that requested the data.<\/p>\n<p>So you just need to keep the clipboard open long enough for the clipboard owner to finish rendering the data format and calling <code>Set\u00adClipboard\u00adData<\/code>.<\/p>\n<p>But how do you know that the clipboard owner has finished rendering the data format?<\/p>\n<p>One idea is to poll by calling <code>Get\u00adClipboard\u00adSequence\u00adNumber<\/code> until the sequence number changes, indicating that somebody (presumably the clipboard owner) called <code>Set\u00adClipboard\u00adData<\/code>. This is unsatisfying because (1) it&#8217;s polling, and (2) if an error occurs which prevents the clipboard owner from producing the data, you&#8217;re just going to be polling forever, unless you create your own timeout.<\/p>\n<p>I have another idea: Call <code>Get\u00adClipboard\u00adOwner<\/code> to obtain the handle of the clipboard owner. This is the window that is busy trying to generate the clipboard data. Send this window a harmless message like <code>WM_<wbr \/>NULL<\/code>. The purpose is not to get a meaningful reply to the message; the purpose is to know &#8220;Hey, this window is responding to messages again!&#8221;<\/p>\n<p>The theory here is that if the window is not responding to messages, then that means that it is still busy producing the data to be put onto the clipboard. Once it starts responding, then the delay-rendering has completed.<\/p>\n<p>This theory holds up if the work is being done by the thread that owns the clipboard owner window. However, if the clipboard owner window is delegating the work to another thread, then this mechanism will report that the clipboard owner processed the <code>WM_NULL<\/code> message, because threads can process inbound sent messages while waiting for an outbound sent message to return.<\/p>\n<p>But hey, it&#8217;s better than nothing.<\/p>\n<pre>if (OpenClipboard(hwnd)) {\r\n    if (IsClipboardFormatAvailable(format)) {\r\n        auto data = GetClipboardData(format);\r\n        if (!data) {\r\n            auto owner = GetClipboardOwner();\r\n            SendMessage(owner, WM_NULL, 0, 0);\r\n            data = GetClipboardData(format);\r\n        }\r\n        if (data) {\r\n            \/\/ yay, we have clipboard data\r\n        }\r\n    }\r\n    CloseClipboard();\r\n}\r\n<\/pre>\n<p>After opening the clipboard, we check whether our desired format is available. If so, we ask for it. If that request failed, then we send the clipboard owner a <code>WM_NULL<\/code> message to wait for it to finish generating the format, and then try a second time. If that second try also fails, then we just give up.<\/p>\n<p>Of course, instead of sending the <code>WM_<wbr \/>NULL<\/code> message with <code>Send\u00adMessage<\/code>, you can use <code>Send\u00adMessage\u00adTimeout<\/code> to apply your own custom timeout. If the <code>Send\u00adMessage\u00adTimeout<\/code> of <code>WM_<wbr \/>NULL<\/code> fails, then you can probably skip calling <code>Get\u00adClipboard\u00adData<\/code> a second time. The thread is probably still busy processing the original request. There&#8217;s an off chance that the thread completed the original request but became hung on something else in the meantime. I don&#8217;t consider this off chance worth the effort to work around. The whole thing is just a workaround anyway.<\/p>\n<p>It turns out that if you go back to the customer&#8217;s original problem, none of these workarounds are needed anyway. We&#8217;ll look at a better solution next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Provide your own delay.<\/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],"class_list":["post-106731","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Provide your own delay.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106731","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=106731"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106731\/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=106731"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106731"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106731"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}