From 1712ab3ba109b2beafa418f0b12d09c836590b2c Mon Sep 17 00:00:00 2001 From: Cecilio Pardo Date: Fri, 27 Sep 2024 23:58:02 +0200 Subject: [PATCH] Implement drag-n-drop for w32 with support for files and text Implement dnd with IDropTarget * lisp/term/w32-win.el (w32-drag-n-drop): changed to handle files or strings * src/w32fns.c (process_dropfiles): new, convert DROPFILES struct to array of strings (w32_createwindow): assign an IDropTarget to each new frame (w32_name_of_message): added new messages (w32_msg_pump): Changed CoInitialize to OleInitialize, needed by the drag-n-drop functions (w32_wnd_proc): new struct w32_drop_target, and w32_drop_target_* functions to implement the IDropTarget interface * src/w32term.c (w32_read_socket): handle WM_EMACS_DROPFILES, WM_EMACS_DROPSTRING, skip WM_EMACS_DROPFILES * src/w32term.h (): add WM_EMACS_DROPFILES, WM_EMACS_DROPSTRING --- lisp/term/w32-win.el | 19 ++-- src/w32fns.c | 202 ++++++++++++++++++++++++++++++++++++++++++- src/w32term.c | 46 ++++++++++ src/w32term.h | 4 +- 4 files changed, 260 insertions(+), 11 deletions(-) diff --git a/lisp/term/w32-win.el b/lisp/term/w32-win.el index b57b3dd3bef..ac85ec8d945 100644 --- a/lisp/term/w32-win.el +++ b/lisp/term/w32-win.el @@ -131,8 +131,11 @@ w32-dropped-file-to-url file-name)) (defun w32-drag-n-drop (event &optional new-frame) - "Edit the files listed in the drag-n-drop EVENT. -Switch to a buffer editing the last file dropped." + "If the drag-n-drop EVENT is for a file or files, edit those +files. Switch to a buffer editing the last file dropped. + +If the EVENT is for text, insert the text at point on the buffer shown +in the target window, or add to kill ring if that buffer is read-only." (interactive "e") (save-excursion ;; Make sure the drop target has positive co-ords @@ -140,6 +143,7 @@ w32-drag-n-drop ;; won't work. (let* ((window (posn-window (event-start event))) (coords (posn-x-y (event-start event))) + (arg (car (cdr (cdr event)))) (x (car coords)) (y (cdr coords))) (if (and (> x 0) (> y 0)) @@ -150,11 +154,12 @@ w32-drag-n-drop (raise-frame) (setq window (selected-window)) - (dnd-handle-multiple-urls - window - (mapcar #'w32-dropped-file-to-url - (car (cdr (cdr event)))) - 'private)))) + (if (stringp arg) + (dnd-insert-text window 'copy arg) + (dnd-handle-multiple-urls + window + (mapcar #'w32-dropped-file-to-url arg) + 'private))))) (defun w32-drag-n-drop-other-frame (event) "Edit the files listed in the drag-n-drop EVENT, in other frames. diff --git a/src/w32fns.c b/src/w32fns.c index 0a3f5c38a58..fdba3524846 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -34,6 +34,12 @@ #define _WIN32_WINNT 0x0600 #include +#define COBJMACROS /* Ask for C definitions for COM. */ +#include +#include +#include +#include + #include "lisp.h" #include "w32term.h" #include "frame.h" @@ -359,6 +365,10 @@ #define WS_EX_NOACTIVATE 0x08000000L static struct w32_display_info *w32_display_info_for_name (Lisp_Object); +static void my_post_msg (W32Msg*, HWND, UINT, WPARAM, LPARAM); +static unsigned int w32_get_modifiers (void); + + /* Let the user specify a display with a frame. nil stands for the selected frame--or, if that is not a w32 frame, the first display on the list. */ @@ -2464,6 +2474,168 @@ w32_createhscrollbar (struct frame *f, struct scroll_bar * bar) return hwnd; } +/* From the DROPFILES struct, extract the list of filenames. Returns a + NULL terminated malloc array of malloc strings that should be freed + by the caller. */ +static char ** +process_dropfiles (DROPFILES *files) +{ + char *start_of_files = (char*)files + files->pFiles; + int count = 0; + char filename[MAX_PATH]; + char **filenames; + + if (files->fWide) + { + WCHAR *p = (WCHAR*)start_of_files; + for ( ; *p; count ++, p += wcslen (p) + 1) + ; + filenames = malloc ((count+1) * sizeof (char*)); + filenames [count] = NULL; + p = (WCHAR*)start_of_files; + for ( int i = 0; *p; p += wcslen (p) + 1, i++) + { + filename_from_utf16 (p, filename); + filenames [i] = xstrdup (filename); + } + } + else + { + char *p = start_of_files; + for ( ; *p; count ++, p += strlen (p) + 1) + ; + filenames = malloc ((count + 1) * sizeof (char*)); + filenames [count] = NULL; + p = start_of_files; + for ( int i = 0; *p; p += strlen(p) + 1, i++) + { + filename_from_ansi (p, filename); + filenames[i] = xstrdup (filename); + } + } + return filenames; +} + +struct w32_drop_target { + /* i_drop_target must be the first member. */ + IDropTarget i_drop_target; + HWND hwnd; +}; + +static HRESULT STDMETHODCALLTYPE +w32_drop_target_QueryInterface (IDropTarget *t, REFIID ri, void **r) +{ + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE +w32_drop_target_AddRef (IDropTarget *This) +{ + return 1; +} + +static ULONG STDMETHODCALLTYPE +w32_drop_target_Release (IDropTarget *This) +{ + struct w32_drop_target *target = (struct w32_drop_target *)This; + xfree( target->i_drop_target.lpVtbl ); + xfree( target ); + return 0; +} + +static HRESULT STDMETHODCALLTYPE +w32_drop_target_DragEnter( IDropTarget *This, IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect = DROPEFFECT_COPY; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +w32_drop_target_DragOver ( IDropTarget *This, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect = DROPEFFECT_COPY; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +w32_drop_target_DragLeave( IDropTarget *This ) +{ + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +w32_drop_target_Drop (IDropTarget *This, IDataObject *pDataObj, + DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + struct w32_drop_target *target = (struct w32_drop_target *)This; + *pdwEffect = DROPEFFECT_COPY; + + W32Msg msg = {0}; + msg.dwModifiers = w32_get_modifiers(); + msg.msg.time = GetMessageTime (); + msg.msg.pt.x = pt.x; + msg.msg.pt.y = pt.y; + + STGMEDIUM stgmedium; + + /* Try for dropped files (CF_HDROP). */ + FORMATETC formatetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + if (SUCCEEDED (IDataObject_GetData (pDataObj, &formatetc, &stgmedium))) + { + if (stgmedium.tymed == TYMED_HGLOBAL) + { + DROPFILES *files = (DROPFILES*)GlobalLock (stgmedium.hGlobal); + if (files) + my_post_msg (&msg, target->hwnd, WM_EMACS_DROPFILES, + 0, (LPARAM)process_dropfiles (files) ); + GlobalUnlock (stgmedium.hGlobal); + } + ReleaseStgMedium (&stgmedium); + return S_OK; + } + + formatetc.cfFormat = CF_UNICODETEXT; + if (SUCCEEDED (IDataObject_GetData (pDataObj, &formatetc, &stgmedium))) + { + if (stgmedium.tymed == TYMED_HGLOBAL) + { + WCHAR *text = (WCHAR*)GlobalLock (stgmedium.hGlobal); + Lisp_Object text_string = from_unicode_buffer (text); + char *utf8 = xstrdup (SSDATA (ENCODE_UTF_8 (text_string))); + my_post_msg (&msg, target->hwnd, WM_EMACS_DROPSTRING, + 0, (LPARAM)utf8 ); + GlobalUnlock (stgmedium.hGlobal); + } + ReleaseStgMedium (&stgmedium); + return S_OK; + } + + formatetc.cfFormat = CF_TEXT; + if (SUCCEEDED (IDataObject_GetData (pDataObj, &formatetc, &stgmedium))) + { + if (stgmedium.tymed == TYMED_HGLOBAL) + { + char *text = (char*)GlobalLock (stgmedium.hGlobal); + + int l = strlen (text); + WCHAR *text_utf16 = xmalloc (sizeof (WCHAR) * l + 1 );; + if (MultiByteToWideChar (CP_ACP, 0, text, l, text_utf16, l + 1)) + { + Lisp_Object text_string = from_unicode_buffer (text_utf16); + char *utf8 = xstrdup (SSDATA (ENCODE_UTF_8 (text_string))); + my_post_msg (&msg, target->hwnd, WM_EMACS_DROPSTRING, + 0, (LPARAM)utf8 ); + } + xfree (text_utf16); + GlobalUnlock (stgmedium.hGlobal); + } + ReleaseStgMedium (&stgmedium); + return S_OK; + } + + return S_OK; +} + static void w32_createwindow (struct frame *f, int *coords) { @@ -2548,7 +2720,23 @@ w32_createwindow (struct frame *f, int *coords) SetWindowLong (hwnd, WND_BACKGROUND_INDEX, FRAME_BACKGROUND_PIXEL (f)); /* Enable drag-n-drop. */ + struct w32_drop_target *drop_target = xmalloc (sizeof (struct w32_drop_target)); + drop_target-> hwnd = hwnd; + + IDropTargetVtbl *vtbl = xmalloc (sizeof (IDropTargetVtbl)); + drop_target->i_drop_target.lpVtbl = vtbl; + vtbl->QueryInterface = w32_drop_target_QueryInterface; + vtbl->AddRef = w32_drop_target_AddRef; + vtbl->Release = w32_drop_target_Release; + vtbl->DragEnter = w32_drop_target_DragEnter; + vtbl->DragOver = w32_drop_target_DragOver; + vtbl->DragLeave = w32_drop_target_DragLeave; + vtbl->Drop = w32_drop_target_Drop; + RegisterDragDrop (hwnd, &drop_target->i_drop_target); + +#if 0 DragAcceptFiles (hwnd, TRUE); +#endif /* Enable system light/dark theme. */ w32_applytheme (hwnd); @@ -3399,6 +3587,8 @@ #define M(msg) { msg, # msg } M (WM_EMACS_PAINT), M (WM_EMACS_IME_STATUS), M (WM_CHAR), + M (WM_EMACS_DROPFILES), + M (WM_EMACS_DROPSTRING), #undef M { 0, 0 } }; @@ -3465,13 +3655,14 @@ w32_msg_pump (deferred_msg * msg_buf) /* Produced by complete_deferred_msg; just ignore. */ break; case WM_EMACS_CREATEWINDOW: - /* Initialize COM for this window. Even though we don't use it, - some third party shell extensions can cause it to be used in + /* Initialize COM for this window. Needed for RegisterDragDrop. + Some third party shell extensions can cause it to be used in system dialogs, which causes a crash if it is not initialized. This is a known bug in Windows, which was fixed long ago, but the patch for XP is not publicly available until XP SP3, and older versions will never be patched. */ - CoInitialize (NULL); + OleInitialize (NULL); + w32_createwindow ((struct frame *) msg.wParam, (int *) msg.lParam); if (!PostThreadMessage (dwMainThreadId, WM_EMACS_DONE, 0, 0)) @@ -5106,7 +5297,9 @@ #define MOUSEEVENTF_FROMTOUCH 0xFF515700 return 0; case WM_MOUSEWHEEL: +#if 0 case WM_DROPFILES: +#endif wmsg.dwModifiers = w32_get_modifiers (); my_post_msg (&wmsg, hwnd, msg, wParam, lParam); signal_user_input (); @@ -5597,7 +5790,10 @@ #define WM_TOUCH 576 } case WM_EMACS_DESTROYWINDOW: + RevokeDragDrop ((HWND) wParam); +#if 0 DragAcceptFiles ((HWND) wParam, FALSE); +#endif return DestroyWindow ((HWND) wParam); case WM_EMACS_HIDE_CARET: diff --git a/src/w32term.c b/src/w32term.c index 62037e3b2cd..e1ff4112a7b 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -3576,6 +3576,7 @@ w32_construct_mouse_wheel (struct input_event *result, W32Msg *msg, return Qnil; } +#if 0 static Lisp_Object w32_construct_drag_n_drop (struct input_event *result, W32Msg *msg, struct frame *f) @@ -3651,6 +3652,7 @@ w32_construct_drag_n_drop (struct input_event *result, W32Msg *msg, return Qnil; } +#endif #if HAVE_W32NOTIFY @@ -5682,12 +5684,56 @@ w32_read_socket (struct terminal *terminal, } break; +#if 0 case WM_DROPFILES: f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); if (f) w32_construct_drag_n_drop (&inev, &msg, f); break; +#endif + + case WM_EMACS_DROPSTRING: + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + if (!f) + break; + XSETFRAME (inev.frame_or_window, f); + inev.kind = DRAG_N_DROP_EVENT; + inev.code = 0; + inev.timestamp = msg.msg.time; + inev.modifiers = msg.dwModifiers; + ScreenToClient (msg.msg.hwnd, &msg.msg.pt); + XSETINT (inev.x, msg.msg.pt.x); + XSETINT (inev.y, msg.msg.pt.y); + inev.arg = make_string ((char*)msg.msg.lParam, strlen((char*)msg.msg.lParam)); + xfree ((void*)msg.msg.lParam); + break; + + case WM_EMACS_DROPFILES: + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + if (!f) + break; + XSETFRAME (inev.frame_or_window, f); + inev.kind = DRAG_N_DROP_EVENT; + inev.code = 0; + inev.timestamp = msg.msg.time; + inev.modifiers = msg.dwModifiers; + ScreenToClient (msg.msg.hwnd, &msg.msg.pt); + XSETINT (inev.x, msg.msg.pt.x); + XSETINT (inev.y, msg.msg.pt.y); + + Lisp_Object files = Qnil; + char **filenames = (char**)msg.msg.lParam; + for (int n = 0; filenames[n]; n++ ) + { + files = Fcons (DECODE_FILE (build_string (filenames[n])), files ); + xfree (filenames[n]); + } + xfree (filenames); + inev.arg = files; + break; + } case WM_HSCROLL: { diff --git a/src/w32term.h b/src/w32term.h index 47be542f570..c1253af47c4 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -710,7 +710,9 @@ #define WM_EMACS_BRINGTOTOP (WM_EMACS_START + 23) #define WM_EMACS_INPUT_READY (WM_EMACS_START + 24) #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) -#define WM_EMACS_END (WM_EMACS_START + 27) +#define WM_EMACS_DROPFILES (WM_EMACS_START + 27) +#define WM_EMACS_DROPSTRING (WM_EMACS_START + 28) +#define WM_EMACS_END (WM_EMACS_START + 29) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) -- 2.35.1.windows.2