Standard message box dialog provides a useful feature: pressing Ctrl-C or Ctrl-Insert in it copies its contents to the clipboard, which is very convenient when reporting problems in the programs using message boxes for error reporting, for example.
Implement support for this, by adding WM_COPY handler to the message box dialog proc, which does the actual copying, and modifying the general dialog box proc to generate WM_COPY when keys above are pressed in a message box dialog. This required adding a new DF_MSGBOX flag, which allows to distinguish message boxes from the other dialogs, which is a bit ugly, but seems to be the best/only way to support this feature.
Signed-off-by: Vadim Zeitlin vz-wine@zeitlins.org --- dlls/user32/controls.h | 5 +- dlls/user32/dialog.c | 18 +++- dlls/user32/msgbox.c | 196 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 3 deletions(-)
diff --git a/dlls/user32/controls.h b/dlls/user32/controls.h index 0b0f1c2801..09ac4d98dd 100644 --- a/dlls/user32/controls.h +++ b/dlls/user32/controls.h @@ -234,10 +234,11 @@ extern UINT MENU_GetMenuBarHeight( HWND hwnd, UINT menubarWidth, UINT xBaseUnit; /* Dialog units (depends on the font) */ UINT yBaseUnit; INT idResult; /* EndDialog() result / default pushbutton ID */ - UINT flags; /* EndDialog() called for this dialog */ + UINT flags; /* Combination of DF_XXX values below */ } DIALOGINFO;
-#define DF_END 0x0001 +#define DF_END 0x0001 /* EndDialog() called for this dialog */ +#define DF_MSGBOX 0x0002 /* Dialog is a message box */
extern DIALOGINFO *DIALOG_get_info( HWND hwnd, BOOL create ) DECLSPEC_HIDDEN; extern INT DIALOG_DoDialogBox( HWND hwnd, HWND owner ) DECLSPEC_HIDDEN; diff --git a/dlls/user32/dialog.c b/dlls/user32/dialog.c index 88c2930c06..552fae2fbe 100644 --- a/dlls/user32/dialog.c +++ b/dlls/user32/dialog.c @@ -810,7 +810,23 @@ INT DIALOG_DoDialogBox( HWND hwnd, HWND owner ) break; } if (!IsWindow( hwnd )) return 0; - if (!(dlgInfo->flags & DF_END) && !IsDialogMessageW( hwnd, &msg)) + if (dlgInfo->flags & DF_END) break; + + /* Special hack for message boxes: allow them to copy their + * contents to clipboard when Ctrl-C or Ctrl-Ins is pressed. */ + if (dlgInfo->flags & DF_MSGBOX) + { + if ((msg.message == WM_CHAR && + LOBYTE(msg.wParam) == 3) || + (msg.message == WM_KEYDOWN && + LOBYTE(msg.wParam) == VK_INSERT && + GetKeyState(VK_CONTROL) < 0)) + { + SendMessageW(hwnd, WM_COPY, 0, 0); + } + } + + if (!IsDialogMessageW( hwnd, &msg)) { TranslateMessage( &msg ); DispatchMessageW( &msg ); diff --git a/dlls/user32/msgbox.c b/dlls/user32/msgbox.c index 457c3ae7d1..5124a4835b 100644 --- a/dlls/user32/msgbox.c +++ b/dlls/user32/msgbox.c @@ -27,6 +27,7 @@ #include "wingdi.h" #include "winternl.h" #include "dlgs.h" +#include "controls.h" #include "user_private.h" #include "resources.h" #include "wine/debug.h" @@ -317,6 +318,191 @@ static void MSGBOX_OnInit(HWND hwnd, LPMSGBOXPARAMSW lpmb) }
+/************************************************************************** + * MSGBOX_CopyToClipboard + * + * Called from MSGBOX_CopyToClipboard() to either build the text string + * representing the message box contents, in the following format: + * + * --------------------------- + * Caption + * --------------------------- + * Text line 1 + * ... + * Text line N + * --------------------------- + * Button_1 ... Button_N + * --------------------------- + * + * or to calculate the size of the buffer needed by this string, if the input + * buffer is null. + * + * Returns the length (not size) of the string that was, or would be, built. + */ +static int MSGBOX_BuildText( HWND hwnd, WCHAR* buffer ) +{ + HWND hItem; + int i; + WCHAR windowText[4096]; + const int windowTextLen = sizeof(windowText) / sizeof(WCHAR); + int len, total_len = 0; + + static const WCHAR separatorLine[] = {'-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','\r','\n',0}; + static int separatorLen = sizeof(separatorLine) / sizeof(WCHAR) - 1; + + static const WCHAR buttonSeparator[] = {' ',' ',' ',0}; + static int buttonSeparatorLen = sizeof(buttonSeparator) / sizeof(WCHAR) - 1; + + static const WCHAR eol[] = {'\r','\n',0}; + static int eolLen = sizeof(eol) / sizeof(WCHAR) - 1; + + /* Top separator line */ + if (buffer) + { + strcpyW(buffer, separatorLine); + buffer += separatorLen; + } + total_len += separatorLen; + + /* Message box caption */ + if (buffer) + { + GetWindowTextW(hwnd, windowText, windowTextLen); + len = strlenW(windowText); + + strcpyW(buffer, windowText); + buffer += len; + + strcpyW(buffer, eol); + buffer += eolLen; + } + else + { + len = GetWindowTextLengthW(hwnd); + } + total_len += len + eolLen; + + /* Second separator line */ + if (buffer) + { + strcpyW(buffer, separatorLine); + buffer += separatorLen; + } + total_len += separatorLen; + + /* Message box text */ + hItem = GetDlgItem(hwnd, MSGBOX_IDTEXT); + if (buffer) + { + GetWindowTextW(hItem, windowText, windowTextLen); + len = strlenW(windowText); + + strcpyW(buffer, windowText); + buffer += len; + + strcpyW(buffer, eol); + buffer += eolLen; + } + else + { + len = GetWindowTextLengthW(hItem); + } + total_len += len + eolLen; + + /* Third separator line */ + if (buffer) + { + strcpyW(buffer, separatorLine); + buffer += separatorLen; + } + total_len += separatorLen; + + /* Buttons line */ + for (i = IDOK; i <= IDCONTINUE; i++) + { + if (i == IDCLOSE) continue; /* No CLOSE button */ + hItem = GetDlgItem(hwnd, i); + if (!(GetWindowLongW(hItem, GWL_STYLE) & WS_VISIBLE)) continue; + + /* Add button text followed by a separator. Notice that we include the + * separator after the last button too, both because it's simpler and + * because this is how the native message box does it. + */ + if (buffer) + { + WCHAR* text = windowText; + + GetWindowTextW(hItem, windowText, windowTextLen); + len = strlenW(windowText); + + /* Don't include mnemonics in the output: all message box buttons + * that use mnemonics at all use them for their first character. + */ + if (windowText[0] == '&') + { + text++; + len--; + } + + strcpyW(buffer, text); + buffer += len; + + strcpyW(buffer, buttonSeparator); + buffer += buttonSeparatorLen; + } + else + { + len = GetWindowTextLengthW(hItem); + } + total_len += len + buttonSeparatorLen; + } + + if (buffer) + { + strcpyW(buffer, eol); + buffer += eolLen; + } + total_len += eolLen; + + /* Bottom separator line */ + if (buffer) + { + strcpyW(buffer, separatorLine); + buffer += separatorLen; + } + total_len += separatorLen; + + return total_len; +} + +/************************************************************************** + * MSGBOX_CopyToClipboard + * + * Called from MSGBOX_DlgProc() and puts message box contents to the clipboard + * in the following format: + * + */ +static void MSGBOX_CopyToClipboard( HWND hwnd ) +{ + HGLOBAL hmem; + WCHAR* buffer; + const int bufferLen = MSGBOX_BuildText(hwnd, NULL); + + hmem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (bufferLen + 1) * sizeof(WCHAR)); + if (!hmem) return; + buffer = GlobalLock(hmem); + MSGBOX_BuildText(hwnd, buffer); + TRACE_(msgbox)("Copying %s to clipboard\n", debugstr_w(buffer)); + GlobalUnlock(buffer); + + if (OpenClipboard(hwnd)) + { + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, hmem); + CloseClipboard(); + } +} + /************************************************************************** * MSGBOX_DlgProc * @@ -328,10 +514,15 @@ static INT_PTR CALLBACK MSGBOX_DlgProc( HWND hwnd, UINT message, switch(message) { case WM_INITDIALOG: { + DIALOGINFO *dlgInfo; LPMSGBOXPARAMSW mbp = (LPMSGBOXPARAMSW)lParam; SetWindowContextHelpId(hwnd, mbp->dwContextHelpId); MSGBOX_OnInit(hwnd, mbp); SetPropA(hwnd, "WINE_MSGBOX_HELPCALLBACK", mbp->lpfnMsgBoxCallback); + + dlgInfo = DIALOG_get_info(hwnd, FALSE); + if (dlgInfo) + dlgInfo->flags |= DF_MSGBOX; break; }
@@ -370,6 +561,11 @@ static INT_PTR CALLBACK MSGBOX_DlgProc( HWND hwnd, UINT message, break; }
+ case WM_COPY: + /* See code sending this message in dialog.c */ + MSGBOX_CopyToClipboard(hwnd); + break; + default: /* Ok. Ignore all the other messages */ TRACE("Message number 0x%04x is being ignored.\n", message);
FWIW, that's bug https://bugs.winehq.org/show_bug.cgi?id=17205 A staging patchset is available at https://github.com/wine-staging/wine-staging/tree/master/patches/user32-msgb...
Regards, Fabian Maurer
On Wed, 22 Jan 2020 20:37:04 +0100 Fabian Maurer dark.shadow4@web.de wrote:
FM> FWIW, that's bug https://bugs.winehq.org/show_bug.cgi?id=17205
Sorry for somehow missing this and thanks for pointing this out!
FM> A staging patchset is available at https://github.com/wine-staging/wine-staging/tree/master/patches/user32-msgb...
Looking at this patch, I see that it's clearly better than mine in some aspects (e.g. it includes tests) but OTOH I'm not sure if using a hook for this is really a good idea, and it seems to have resulted in the problems as discussed at the bug above. There is also another small but annoying difference: the other patch doesn't take Ctrl-Ins into account, even though it works in the same way as Ctrl-C under native MSW.
So what should/can I do now? Would it be worth to update my patch to combine it with the tests from the patch by Alistair Leslie-Hughes? Also, mostly for the future, should I have submitted this patch to Wine staging instead of here?
Thanks in advance, VZ
Hi Vadim,
On 23/1/20 10:40 am, Vadim Zeitlin wrote:
Looking at this patch, I see that it's clearly better than mine in some aspects (e.g. it includes tests) but OTOH I'm not sure if using a hook for this is really a good idea, and it seems to have resulted in the problems as discussed at the bug above. There is also another small but annoying difference: the other patch doesn't take Ctrl-Ins into account, even though it works in the same way as Ctrl-C under native MSW.
When I developed this patch, I didn't know about Ctrl-Ins acting the same as Ctrl-C.
So what should/can I do now? Would it be worth to update my patch to combine it with the tests from the patch by Alistair Leslie-Hughes? Also, mostly for the future, should I have submitted this patch to Wine staging instead of here?
The only issue I know about getting these patches upstream is the tests. They need to be updated so they will work on any locale not just English.
If you want to write the patch to allow "Ctrl-Ins", that extends the current patchset, I can put it in staging for you. Then all that would be required is a new set of tests.
Regards Alistair.
On 23/01/2020 09:11, Alistair Leslie-Hughes wrote:
Hi Vadim,
On 23/1/20 10:40 am, Vadim Zeitlin wrote:
Looking at this patch, I see that it's clearly better than mine in some aspects (e.g. it includes tests) but OTOH I'm not sure if using a hook for this is really a good idea, and it seems to have resulted in the problems as discussed at the bug above. There is also another small but annoying difference: the other patch doesn't take Ctrl-Ins into account, even though it works in the same way as Ctrl-C under native MSW.
When I developed this patch, I didn't know about Ctrl-Ins acting the same as Ctrl-C.
So what should/can I do now? Would it be worth to update my patch to combine it with the tests from the patch by Alistair Leslie-Hughes? Also, mostly for the future, should I have submitted this patch to Wine staging instead of here?
The only issue I know about getting these patches upstream is the tests. They need to be updated so they will work on any locale not just English.
If you want to write the patch to allow "Ctrl-Ins", that extends the current patchset, I can put it in staging for you. Then all that would be required is a new set of tests.
Regards Alistair.
Also FWIW, I haven't found any problems with the staging patch for a long time ever since it has been updated to use a thread local hook instead of global, so that's not an issue.
On Thu, Jan 23, 2020 at 3:30 PM Gabriel Ivăncescu gabrielopcode@gmail.com wrote:
On 23/01/2020 09:11, Alistair Leslie-Hughes wrote:
Hi Vadim,
On 23/1/20 10:40 am, Vadim Zeitlin wrote:
Looking at this patch, I see that it's clearly better than mine in
some
aspects (e.g. it includes tests) but OTOH I'm not sure if using a hook
for
this is really a good idea, and it seems to have resulted in the
problems
as discussed at the bug above. There is also another small but annoying difference: the other patch doesn't take Ctrl-Ins into account, even
though
it works in the same way as Ctrl-C under native MSW.
When I developed this patch, I didn't know about Ctrl-Ins acting the same as Ctrl-C.
So what should/can I do now? Would it be worth to update my patch to combine it with the tests from the patch by Alistair Leslie-Hughes?
Also,
mostly for the future, should I have submitted this patch to Wine
staging
instead of here?
The only issue I know about getting these patches upstream is the tests. They need to be updated so they will work on any locale not just
English.
If you want to write the patch to allow "Ctrl-Ins", that extends the current patchset, I can put it in staging for you. Then all that would be required is a new set of tests.
Regards Alistair.
Also FWIW, I haven't found any problems with the staging patch for a long time ever since it has been updated to use a thread local hook instead of global, so that's not an issue.
Both hook and changing general dialog code just for that is not great. Is there really no way to intercept this? Does actual window procedure still receive WM_KEY*?
On Thu, 23 Jan 2020 15:44:44 +0300 Nikolay Sivov bunglehead@gmail.com wrote:
NS> > On 23/01/2020 09:11, Alistair Leslie-Hughes wrote: NS> > > Hi Vadim, [...] NS> > > When I developed this patch, I didn't know about Ctrl-Ins acting the NS> > > same as Ctrl-C.
Hi Alistair and Nikolay,
I'm particularly partial to using Ctrl-Ins as I'm too used to using it myself and my motivation for writing this patch was to improve Wine for my personal use, so I'd really like to see this key combination supported too. Of course, it's trivial to update your patch to do it by reusing the condition from mine i.e.:
+ if ((msg.message == WM_CHAR && + LOBYTE(msg.wParam) == 3) || + (msg.message == WM_KEYDOWN && + LOBYTE(msg.wParam) == VK_INSERT && + GetKeyState(VK_CONTROL) < 0))
But unfortunately I have no idea about how to update the tests to pass for any locale...
And I still don't know whether we should be using a hook here, which is IMHO is also an important question.
NS> Both hook and changing general dialog code just for that is not great.
I definitely agree with this, but unfortunately I couldn't find any better way and I don't think it's possible to do it purely locally, i.e. only at the level of message box itself, because pressing Ctrl-C (or Ins) is supposed to work even when the focus is not on the message box (because it never is). I guess we could subclass all the message box children to propagate this message upwards, but this doesn't seem to be really appealing neither.
NS> Is there really no way to intercept this?
Not at message box level, AFAICS. FWIW native MSW itself seems to do something similar to my patch.
NS> Does actual window procedure still receive WM_KEY*?
Which one? The message box doesn't, and never did. The child window does receive it, the same as before. In my patch this happens because I intentionally don't stop processing after doing SendMessage(WM_COPY), even if it's handled. Again, this is done in order to be consistent with the native behaviour.
Regards, VZ