samhocevar / portable-file-dialogs

💬 Portable GUI dialogs library, C++11, single-header
Do What The F*ck You Want To Public License
1.03k stars 99 forks source link

killing certain dialogs blocks indefinitely on Windows #69

Closed EvanBalster closed 2 years ago

EvanBalster commented 2 years ago

Messages offering multiple choices and save/load dialogs cause the kill() method to block until the user makes a choice.

To reproduce the problem:

pfd::message message("Persistent message!",
    "This message should vanish after 2 seconds", pfd::choice::yes_no);
std::this_thread::sleep_for(std::chrono::seconds(2));
message.kill();
message.result();

Using pfd::choice::ok prevents this issue for some reason.

Internally, the kill() function on Windows is implemented in a very strange way: before creating a dialog, a snapshot of all currently-open windows is taken using EnumWindows. Later, kill takes another snapshot and sends a close message to all windows created between the first snapshot and the second. This would seem to pose the risk of interfering with windows not spawned by this library, and also doesn't seem to be effective at killing those which are.

samhocevar commented 2 years ago

Thanks for the report. The problem is that the WM_CLOSE message does not work in case of a Yes/No or Abort/Retry/Ignore message. I have pushed a fix to the main branch.

I’m curious as to why you find the kill() implementation strange? It takes care to only close windows created by the worker thread, and that thread is blocked inside the MessageBoxW() call. I don’t believe anything else could spawn a window there.

EvanBalster commented 2 years ago

I didn't notice the thread restriction.

This issue extended to save file and open file dialogs. I will test whether those are killable now.

samhocevar commented 2 years ago

Indeed, the issue persists for load/save dialogs. These appear to be very hard to kill. So far I have tried EndDialog(), DestroyWindow(), and a variety of system message, to no avail.

EvanBalster commented 2 years ago

Is this a useful clue? https://stackoverflow.com/a/7068229

EvanBalster commented 2 years ago

I did a bit of digging and determined that the GetOpenFileNameW is not associated with the same thread ID as the calling thread. It exists as an HWND that can be canceled asynchronously, but it sounds as if registering a LPOFNHOOKPROC is the only straightforward way (in the legacy API) to identify that window.

The hook procedure provides a handle to a child window of the dialog, and might be useful for solving some other issues such as https://github.com/samhocevar/portable-file-dialogs/issues/43.

EvanBalster commented 2 years ago

@samhocevar I notice your fix for model dialogs was committed to branch master rather than main: https://github.com/samhocevar/portable-file-dialogs/compare/master