From: Vadim Zeitlin Subject: [PATCH] user32/msgbox: Implement copying contents to clipboard Message-Id: <20200122185944.27657-1-vz-wine@zeitlins.org> Date: Wed, 22 Jan 2020 19:59:44 +0100 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 --- 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); -- 2.24.0.155.gd9f6f3b619