xref: /vim-8.2.3635/src/GvimExt/gvimext.cpp (revision d0bce504)
1 /* vi:set ts=8 sts=4 sw=4:
2  *
3  * VIM - Vi IMproved	gvimext by Tianmiao Hu
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  */
8 
9 /*
10  * gvimext is a DLL which is used for the "Edit with Vim" context menu
11  * extension.  It implements a MS defined interface with the Shell.
12  *
13  * If you have any questions or any suggestions concerning gvimext, please
14  * contact Tianmiao Hu: [email protected].
15  */
16 
17 #include "gvimext.h"
18 
19 static char *searchpath(char *name);
20 
21 // Always get an error while putting the following stuff to the
22 // gvimext.h file as class protected variables, give up and
23 // declare them as global stuff
24 FORMATETC fmte = {CF_HDROP,
25 		  (DVTARGETDEVICE FAR *)NULL,
26 		  DVASPECT_CONTENT,
27 		  -1,
28 		  TYMED_HGLOBAL
29 		 };
30 STGMEDIUM medium;
31 HRESULT hres = 0;
32 UINT cbFiles = 0;
33 
34 /* The buffers size used to be MAX_PATH (260 bytes), but that's not always
35  * enough */
36 #define BUFSIZE 1100
37 
38 //
39 // Get the name of the Gvim executable to use, with the path.
40 // When "runtime" is non-zero, we were called to find the runtime directory.
41 // Returns the path in name[BUFSIZE].  It's empty when it fails.
42 //
43     static void
44 getGvimName(char *name, int runtime)
45 {
46     HKEY	keyhandle;
47     DWORD	hlen;
48 
49     // Get the location of gvim from the registry.
50     name[0] = 0;
51     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
52 				       KEY_READ, &keyhandle) == ERROR_SUCCESS)
53     {
54 	hlen = BUFSIZE;
55 	if (RegQueryValueEx(keyhandle, "path", 0, NULL, (BYTE *)name, &hlen)
56 							     != ERROR_SUCCESS)
57 	    name[0] = 0;
58 	else
59 	    name[hlen] = 0;
60 	RegCloseKey(keyhandle);
61     }
62 
63     // Registry didn't work, use the search path.
64     if (name[0] == 0)
65 	strcpy(name, searchpath((char *)"gvim.exe"));
66 
67     if (!runtime)
68     {
69 	// Only when looking for the executable, not the runtime dir, we can
70 	// search for the batch file or a name without a path.
71 	if (name[0] == 0)
72 	    strcpy(name, searchpath((char *)"gvim.bat"));
73 	if (name[0] == 0)
74 	    strcpy(name, "gvim");	// finds gvim.bat or gvim.exe
75     }
76 }
77 
78     static void
79 getGvimInvocation(char *name, int runtime)
80 {
81     getGvimName(name, runtime);
82     // avoid that Vim tries to expand wildcards in the file names
83     strcat(name, " --literal");
84 }
85 
86     static void
87 getGvimInvocationW(wchar_t *nameW)
88 {
89     char *name;
90 
91     name = (char *)malloc(BUFSIZE);
92     getGvimInvocation(name, 0);
93     mbstowcs(nameW, name, BUFSIZE);
94     free(name);
95 }
96 
97 //
98 // Get the Vim runtime directory into buf[BUFSIZE].
99 // The result is empty when it failed.
100 // When it works, the path ends in a slash or backslash.
101 //
102     static void
103 getRuntimeDir(char *buf)
104 {
105     int		idx;
106 
107     getGvimName(buf, 1);
108     if (buf[0] != 0)
109     {
110 	// When no path found, use the search path to expand it.
111 	if (strchr(buf, '/') == NULL && strchr(buf, '\\') == NULL)
112 	    strcpy(buf, searchpath(buf));
113 
114 	// remove "gvim.exe" from the end
115 	for (idx = (int)strlen(buf) - 1; idx >= 0; idx--)
116 	    if (buf[idx] == '\\' || buf[idx] == '/')
117 	    {
118 		buf[idx + 1] = 0;
119 		break;
120 	    }
121     }
122 }
123 
124 HBITMAP IconToBitmap(HICON hIcon, HBRUSH hBackground, int width, int height)
125 {
126 	HDC hDC = GetDC(NULL);
127 	HDC hMemDC = CreateCompatibleDC(hDC);
128 	HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, width, height);
129 	HBITMAP hResultBmp = NULL;
130 	HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
131 
132 	DrawIconEx(hMemDC, 0, 0, hIcon, width, height, 0, hBackground, DI_NORMAL);
133 
134 	hResultBmp = hMemBmp;
135 	hMemBmp = NULL;
136 
137 	SelectObject(hMemDC, hOrgBMP);
138 	DeleteDC(hMemDC);
139 	ReleaseDC(NULL, hDC);
140 	DestroyIcon(hIcon);
141 	return hResultBmp;
142 }
143 
144 //
145 // GETTEXT: translated messages and menu entries
146 //
147 #ifndef FEAT_GETTEXT
148 # define _(x)  x
149 #else
150 # define _(x)  (*dyn_libintl_gettext)(x)
151 # define VIMPACKAGE "vim"
152 # ifndef GETTEXT_DLL
153 #  define GETTEXT_DLL "libintl.dll"
154 #  define GETTEXT_DLL_ALT "libintl-8.dll"
155 # endif
156 
157 // Dummy functions
158 static char *null_libintl_gettext(const char *);
159 static char *null_libintl_textdomain(const char *);
160 static char *null_libintl_bindtextdomain(const char *, const char *);
161 static int dyn_libintl_init(char *dir);
162 static void dyn_libintl_end(void);
163 
164 static HINSTANCE hLibintlDLL = 0;
165 static char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext;
166 static char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain;
167 static char *(*dyn_libintl_bindtextdomain)(const char *, const char *)
168 						= null_libintl_bindtextdomain;
169 
170 //
171 // Attempt to load libintl.dll.  If it doesn't work, use dummy functions.
172 // "dir" is the directory where the libintl.dll might be.
173 // Return 1 for success, 0 for failure.
174 //
175     static int
176 dyn_libintl_init(char *dir)
177 {
178     int		i;
179     static struct
180     {
181 	char	    *name;
182 	FARPROC	    *ptr;
183     } libintl_entry[] =
184     {
185 	{(char *)"gettext",		(FARPROC*)&dyn_libintl_gettext},
186 	{(char *)"textdomain",		(FARPROC*)&dyn_libintl_textdomain},
187 	{(char *)"bindtextdomain",	(FARPROC*)&dyn_libintl_bindtextdomain},
188 	{NULL, NULL}
189     };
190     DWORD	len, len2;
191     LPWSTR	buf = NULL;
192     LPWSTR	buf2 = NULL;
193 
194     // No need to initialize twice.
195     if (hLibintlDLL)
196 	return 1;
197 
198     // Load gettext library from $VIMRUNTIME\GvimExt{64,32} directory.
199     // Add the directory to $PATH temporarily.
200     len = GetEnvironmentVariableW(L"PATH", NULL, 0);
201     len2 = MAX_PATH + 1 + len;
202     buf = (LPWSTR)malloc(len * sizeof(WCHAR));
203     buf2 = (LPWSTR)malloc(len2 * sizeof(WCHAR));
204     if (buf != NULL && buf2 != NULL)
205     {
206 	GetEnvironmentVariableW(L"PATH", buf, len);
207 # ifdef _WIN64
208 	_snwprintf(buf2, len2, L"%S\\GvimExt64;%s", dir, buf);
209 # else
210 	_snwprintf(buf2, len2, L"%S\\GvimExt32;%s", dir, buf);
211 # endif
212 	SetEnvironmentVariableW(L"PATH", buf2);
213 	hLibintlDLL = LoadLibrary(GETTEXT_DLL);
214 # ifdef GETTEXT_DLL_ALT
215 	if (!hLibintlDLL)
216 	    hLibintlDLL = LoadLibrary(GETTEXT_DLL_ALT);
217 # endif
218 	SetEnvironmentVariableW(L"PATH", buf);
219     }
220     free(buf);
221     free(buf2);
222     if (!hLibintlDLL)
223 	return 0;
224 
225     // Get the addresses of the functions we need.
226     for (i = 0; libintl_entry[i].name != NULL
227 					 && libintl_entry[i].ptr != NULL; ++i)
228     {
229 	if ((*libintl_entry[i].ptr = GetProcAddress(hLibintlDLL,
230 					      libintl_entry[i].name)) == NULL)
231 	{
232 	    dyn_libintl_end();
233 	    return 0;
234 	}
235     }
236     return 1;
237 }
238 
239     static void
240 dyn_libintl_end(void)
241 {
242     if (hLibintlDLL)
243 	FreeLibrary(hLibintlDLL);
244     hLibintlDLL			= NULL;
245     dyn_libintl_gettext		= null_libintl_gettext;
246     dyn_libintl_textdomain	= null_libintl_textdomain;
247     dyn_libintl_bindtextdomain	= null_libintl_bindtextdomain;
248 }
249 
250     static char *
251 null_libintl_gettext(const char *msgid)
252 {
253     return (char *)msgid;
254 }
255 
256     static char *
257 null_libintl_bindtextdomain(const char * /* domainname */, const char * /* dirname */)
258 {
259     return NULL;
260 }
261 
262     static char *
263 null_libintl_textdomain(const char*  /* domainname */)
264 {
265     return NULL;
266 }
267 
268 //
269 // Setup for translating strings.
270 //
271     static void
272 dyn_gettext_load(void)
273 {
274     char    szBuff[BUFSIZE];
275     DWORD   len;
276 
277     // Try to locate the runtime files.  The path is used to find libintl.dll
278     // and the vim.mo files.
279     getRuntimeDir(szBuff);
280     if (szBuff[0] != 0)
281     {
282 	len = (DWORD)strlen(szBuff);
283 	if (dyn_libintl_init(szBuff))
284 	{
285 	    strcpy(szBuff + len, "lang");
286 
287 	    (*dyn_libintl_bindtextdomain)(VIMPACKAGE, szBuff);
288 	    (*dyn_libintl_textdomain)(VIMPACKAGE);
289 	}
290     }
291 }
292 
293     static void
294 dyn_gettext_free(void)
295 {
296     dyn_libintl_end();
297 }
298 #endif // FEAT_GETTEXT
299 
300 //
301 // Global variables
302 //
303 UINT      g_cRefThisDll = 0;    // Reference count of this DLL.
304 HINSTANCE g_hmodThisDll = NULL;	// Handle to this DLL itself.
305 
306 
307 //---------------------------------------------------------------------------
308 // DllMain
309 //---------------------------------------------------------------------------
310 extern "C" int APIENTRY
311 DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID  /* lpReserved */)
312 {
313     switch (dwReason)
314     {
315     case DLL_PROCESS_ATTACH:
316 	// Extension DLL one-time initialization
317 	g_hmodThisDll = hInstance;
318 	break;
319 
320     case DLL_PROCESS_DETACH:
321 	break;
322     }
323 
324     return 1;   // ok
325 }
326 
327     static void
328 inc_cRefThisDLL()
329 {
330 #ifdef FEAT_GETTEXT
331     if (g_cRefThisDll == 0)
332 	dyn_gettext_load();
333 #endif
334     InterlockedIncrement((LPLONG)&g_cRefThisDll);
335 }
336 
337     static void
338 dec_cRefThisDLL()
339 {
340 #ifdef FEAT_GETTEXT
341     if (InterlockedDecrement((LPLONG)&g_cRefThisDll) == 0)
342 	dyn_gettext_free();
343 #else
344     InterlockedDecrement((LPLONG)&g_cRefThisDll);
345 #endif
346 }
347 
348 //---------------------------------------------------------------------------
349 // DllCanUnloadNow
350 //---------------------------------------------------------------------------
351 
352 STDAPI DllCanUnloadNow(void)
353 {
354     return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
355 }
356 
357 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
358 {
359     *ppvOut = NULL;
360 
361     if (IsEqualIID(rclsid, CLSID_ShellExtension))
362     {
363 	CShellExtClassFactory *pcf = new CShellExtClassFactory;
364 
365 	return pcf->QueryInterface(riid, ppvOut);
366     }
367 
368     return CLASS_E_CLASSNOTAVAILABLE;
369 }
370 
371 CShellExtClassFactory::CShellExtClassFactory()
372 {
373     m_cRef = 0L;
374 
375     inc_cRefThisDLL();
376 }
377 
378 CShellExtClassFactory::~CShellExtClassFactory()
379 {
380     dec_cRefThisDLL();
381 }
382 
383 STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid,
384 						   LPVOID FAR *ppv)
385 {
386     *ppv = NULL;
387 
388     // any interface on this object is the object pointer
389 
390     if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
391     {
392 	*ppv = (LPCLASSFACTORY)this;
393 
394 	AddRef();
395 
396 	return NOERROR;
397     }
398 
399     return E_NOINTERFACE;
400 }
401 
402 STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef()
403 {
404     return InterlockedIncrement((LPLONG)&m_cRef);
405 }
406 
407 STDMETHODIMP_(ULONG) CShellExtClassFactory::Release()
408 {
409     if (InterlockedDecrement((LPLONG)&m_cRef))
410 	return m_cRef;
411 
412     delete this;
413 
414     return 0L;
415 }
416 
417 STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
418 						      REFIID riid,
419 						      LPVOID *ppvObj)
420 {
421     *ppvObj = NULL;
422 
423     // Shell extensions typically don't support aggregation (inheritance)
424 
425     if (pUnkOuter)
426 	return CLASS_E_NOAGGREGATION;
427 
428     // Create the main shell extension object.  The shell will then call
429     // QueryInterface with IID_IShellExtInit--this is how shell extensions are
430     // initialized.
431 
432     LPCSHELLEXT pShellExt = new CShellExt();  // create the CShellExt object
433 
434     if (NULL == pShellExt)
435 	return E_OUTOFMEMORY;
436 
437     return pShellExt->QueryInterface(riid, ppvObj);
438 }
439 
440 
441 STDMETHODIMP CShellExtClassFactory::LockServer(BOOL  /* fLock */)
442 {
443     return NOERROR;
444 }
445 
446 // *********************** CShellExt *************************
447 CShellExt::CShellExt()
448 {
449     m_cRef = 0L;
450     m_pDataObj = NULL;
451 
452     inc_cRefThisDLL();
453 
454     LoadMenuIcon();
455 }
456 
457 CShellExt::~CShellExt()
458 {
459     if (m_pDataObj)
460 	m_pDataObj->Release();
461 
462     dec_cRefThisDLL();
463 
464     if (m_hVimIconBitmap)
465 	DeleteObject(m_hVimIconBitmap);
466 }
467 
468 STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv)
469 {
470     *ppv = NULL;
471 
472     if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
473     {
474 	*ppv = (LPSHELLEXTINIT)this;
475     }
476     else if (IsEqualIID(riid, IID_IContextMenu))
477     {
478 	*ppv = (LPCONTEXTMENU)this;
479     }
480 
481     if (*ppv)
482     {
483 	AddRef();
484 
485 	return NOERROR;
486     }
487 
488     return E_NOINTERFACE;
489 }
490 
491 STDMETHODIMP_(ULONG) CShellExt::AddRef()
492 {
493     return InterlockedIncrement((LPLONG)&m_cRef);
494 }
495 
496 STDMETHODIMP_(ULONG) CShellExt::Release()
497 {
498 
499     if (InterlockedDecrement((LPLONG)&m_cRef))
500 	return m_cRef;
501 
502     delete this;
503 
504     return 0L;
505 }
506 
507 
508 //
509 //  FUNCTION: CShellExt::Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY)
510 //
511 //  PURPOSE: Called by the shell when initializing a context menu or property
512 //	     sheet extension.
513 //
514 //  PARAMETERS:
515 //    pIDFolder - Specifies the parent folder
516 //    pDataObj  - Specifies the set of items selected in that folder.
517 //    hRegKey   - Specifies the type of the focused item in the selection.
518 //
519 //  RETURN VALUE:
520 //
521 //    NOERROR in all cases.
522 //
523 //  COMMENTS:   Note that at the time this function is called, we don't know
524 //		(or care) what type of shell extension is being initialized.
525 //		It could be a context menu or a property sheet.
526 //
527 
528 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST  /* pIDFolder */,
529 				   LPDATAOBJECT pDataObj,
530 				   HKEY  /* hRegKey */)
531 {
532     // Initialize can be called more than once
533     if (m_pDataObj)
534 	m_pDataObj->Release();
535 
536     // duplicate the object pointer and registry handle
537 
538     if (pDataObj)
539     {
540 	m_pDataObj = pDataObj;
541 	pDataObj->AddRef();
542     }
543 
544     return NOERROR;
545 }
546 
547 
548 //
549 //  FUNCTION: CShellExt::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
550 //
551 //  PURPOSE: Called by the shell just before the context menu is displayed.
552 //	     This is where you add your specific menu items.
553 //
554 //  PARAMETERS:
555 //    hMenu      - Handle to the context menu
556 //    indexMenu  - Index of where to begin inserting menu items
557 //    idCmdFirst - Lowest value for new menu ID's
558 //    idCmtLast  - Highest value for new menu ID's
559 //    uFlags     - Specifies the context of the menu event
560 //
561 //  RETURN VALUE:
562 //
563 //
564 //  COMMENTS:
565 //
566 
567 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
568 					 UINT indexMenu,
569 					 UINT idCmdFirst,
570 					 UINT  /* idCmdLast */,
571 					 UINT  /* uFlags */)
572 {
573     UINT idCmd = idCmdFirst;
574 
575     hres = m_pDataObj->GetData(&fmte, &medium);
576     if (medium.hGlobal)
577 	cbFiles = DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, 0, 0);
578 
579     // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
580 
581     // Initialize m_cntOfHWnd to 0
582     m_cntOfHWnd = 0;
583 
584     HKEY keyhandle;
585     bool showExisting = true;
586     bool showIcons = true;
587 
588     // Check whether "Edit with existing Vim" entries are disabled.
589     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
590 				       KEY_READ, &keyhandle) == ERROR_SUCCESS)
591     {
592 	if (RegQueryValueEx(keyhandle, "DisableEditWithExisting", 0, NULL,
593 						 NULL, NULL) == ERROR_SUCCESS)
594 	    showExisting = false;
595 	if (RegQueryValueEx(keyhandle, "DisableContextMenuIcons", 0, NULL,
596 						 NULL, NULL) == ERROR_SUCCESS)
597 	    showIcons = false;
598 	RegCloseKey(keyhandle);
599     }
600 
601     // Retrieve all the vim instances, unless disabled.
602     if (showExisting)
603 	EnumWindows(EnumWindowsProc, (LPARAM)this);
604 
605     MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
606     mii.fMask = MIIM_STRING | MIIM_ID;
607     if (showIcons)
608     {
609 	mii.fMask |= MIIM_BITMAP;
610 	mii.hbmpItem = m_hVimIconBitmap;
611     }
612 
613     if (cbFiles > 1)
614     {
615 	mii.wID = idCmd++;
616 	mii.dwTypeData = _("Edit with &multiple Vims");
617 	mii.cch = lstrlen(mii.dwTypeData);
618 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
619 
620 	mii.wID = idCmd++;
621 	mii.dwTypeData = _("Edit with single &Vim");
622 	mii.cch = lstrlen(mii.dwTypeData);
623 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
624 
625 	if (cbFiles <= 4)
626 	{
627 	    // Can edit up to 4 files in diff mode
628 	    mii.wID = idCmd++;
629 	    mii.dwTypeData = _("Diff with Vim");
630 	    mii.cch = lstrlen(mii.dwTypeData);
631 	    InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
632 	    m_edit_existing_off = 3;
633 	}
634 	else
635 	    m_edit_existing_off = 2;
636 
637     }
638     else
639     {
640 	mii.wID = idCmd++;
641 	mii.dwTypeData = _("Edit with &Vim");
642 	mii.cch = lstrlen(mii.dwTypeData);
643 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
644 	m_edit_existing_off = 1;
645     }
646 
647     HMENU hSubMenu = NULL;
648     if (m_cntOfHWnd > 1)
649     {
650 	hSubMenu = CreatePopupMenu();
651 	mii.fMask |= MIIM_SUBMENU;
652 	mii.wID = idCmd;
653 	mii.dwTypeData = _("Edit with existing Vim");
654 	mii.cch = lstrlen(mii.dwTypeData);
655 	mii.hSubMenu = hSubMenu;
656 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
657 	mii.fMask = mii.fMask & ~MIIM_SUBMENU;
658 	mii.hSubMenu = NULL;
659     }
660     // Now display all the vim instances
661     for (int i = 0; i < m_cntOfHWnd; i++)
662     {
663 	char title[BUFSIZE];
664 	char temp[BUFSIZE];
665 	int index;
666 	HMENU hmenu;
667 
668 	// Obtain window title, continue if can not
669 	if (GetWindowText(m_hWnd[i], title, BUFSIZE - 1) == 0)
670 	    continue;
671 	// Truncate the title before the path, keep the file name
672 	char *pos = strchr(title, '(');
673 	if (pos != NULL)
674 	{
675 	    if (pos > title && pos[-1] == ' ')
676 		--pos;
677 	    *pos = 0;
678 	}
679 	// Now concatenate
680 	if (m_cntOfHWnd > 1)
681 	    temp[0] = '\0';
682 	else
683 	{
684 	    strncpy(temp, _("Edit with existing Vim - "), BUFSIZE - 1);
685 	    temp[BUFSIZE - 1] = '\0';
686 	}
687 	strncat(temp, title, BUFSIZE - 1 - strlen(temp));
688 	temp[BUFSIZE - 1] = '\0';
689 
690 	mii.wID = idCmd++;
691 	mii.dwTypeData = temp;
692 	mii.cch = lstrlen(mii.dwTypeData);
693 	if (m_cntOfHWnd > 1)
694 	{
695 	    hmenu = hSubMenu;
696 	    index = i;
697 	}
698 	else
699 	{
700 	    hmenu = hMenu;
701 	    index = indexMenu++;
702 	}
703 	InsertMenuItem(hmenu, index, TRUE, &mii);
704     }
705     // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
706 
707     // Must return number of menu items we added.
708     return ResultFromShort(idCmd-idCmdFirst);
709 }
710 
711 //
712 //  FUNCTION: CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO)
713 //
714 //  PURPOSE: Called by the shell after the user has selected on of the
715 //	     menu items that was added in QueryContextMenu().
716 //
717 //  PARAMETERS:
718 //    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
719 //
720 //  RETURN VALUE:
721 //
722 //
723 //  COMMENTS:
724 //
725 
726 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
727 {
728     HRESULT hr = E_INVALIDARG;
729 
730     // If HIWORD(lpcmi->lpVerb) then we have been called programmatically
731     // and lpVerb is a command that should be invoked.  Otherwise, the shell
732     // has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has
733     // selected.  Actually, it's (menu ID - idCmdFirst) from QueryContextMenu().
734     if (!HIWORD(lpcmi->lpVerb))
735     {
736 	UINT idCmd = LOWORD(lpcmi->lpVerb);
737 
738 	if (idCmd >= m_edit_existing_off)
739 	{
740 	    // Existing with vim instance
741 	    hr = PushToWindow(lpcmi->hwnd,
742 		    lpcmi->lpDirectory,
743 		    lpcmi->lpVerb,
744 		    lpcmi->lpParameters,
745 		    lpcmi->nShow,
746 		    idCmd - m_edit_existing_off);
747 	}
748 	else
749 	{
750 	    switch (idCmd)
751 	    {
752 		case 0:
753 		    hr = InvokeGvim(lpcmi->hwnd,
754 			    lpcmi->lpDirectory,
755 			    lpcmi->lpVerb,
756 			    lpcmi->lpParameters,
757 			    lpcmi->nShow);
758 		    break;
759 		case 1:
760 		    hr = InvokeSingleGvim(lpcmi->hwnd,
761 			    lpcmi->lpDirectory,
762 			    lpcmi->lpVerb,
763 			    lpcmi->lpParameters,
764 			    lpcmi->nShow,
765 			    0);
766 		    break;
767 		case 2:
768 		    hr = InvokeSingleGvim(lpcmi->hwnd,
769 			    lpcmi->lpDirectory,
770 			    lpcmi->lpVerb,
771 			    lpcmi->lpParameters,
772 			    lpcmi->nShow,
773 			    1);
774 		    break;
775 	    }
776 	}
777     }
778     return hr;
779 }
780 
781 STDMETHODIMP CShellExt::PushToWindow(HWND  /* hParent */,
782 				   LPCSTR  /* pszWorkingDir */,
783 				   LPCSTR  /* pszCmd */,
784 				   LPCSTR  /* pszParam */,
785 				   int  /* iShowCmd */,
786 				   int idHWnd)
787 {
788     HWND hWnd = m_hWnd[idHWnd];
789 
790     // Show and bring vim instance to foreground
791     if (IsIconic(hWnd) != 0)
792 	ShowWindow(hWnd, SW_RESTORE);
793     else
794 	ShowWindow(hWnd, SW_SHOW);
795     SetForegroundWindow(hWnd);
796 
797     // Post the selected files to the vim instance
798     PostMessage(hWnd, WM_DROPFILES, (WPARAM)medium.hGlobal, 0);
799 
800     return NOERROR;
801 }
802 
803 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR  /* idCmd */,
804 					 UINT uFlags,
805 					 UINT FAR * /* reserved */,
806 					 LPSTR pszName,
807 					 UINT cchMax)
808 {
809     if (uFlags == GCS_HELPTEXT && cchMax > 35)
810 	lstrcpy(pszName, _("Edits the selected file(s) with Vim"));
811 
812     return NOERROR;
813 }
814 
815 BOOL CALLBACK CShellExt::EnumWindowsProc(HWND hWnd, LPARAM lParam)
816 {
817     char temp[BUFSIZE];
818 
819     // First do a bunch of check
820     // No invisible window
821     if (!IsWindowVisible(hWnd)) return TRUE;
822     // No child window ???
823     // if (GetParent(hWnd)) return TRUE;
824     // Class name should be Vim, if failed to get class name, return
825     if (GetClassName(hWnd, temp, sizeof(temp)) == 0)
826 	return TRUE;
827     // Compare class name to that of vim, if not, return
828     if (_strnicmp(temp, "vim", sizeof("vim")) != 0)
829 	return TRUE;
830     // First check if the number of vim instance exceeds MAX_HWND
831     CShellExt *cs = (CShellExt*) lParam;
832     if (cs->m_cntOfHWnd >= MAX_HWND) return TRUE;
833     // Now we get the vim window, put it into some kind of array
834     cs->m_hWnd[cs->m_cntOfHWnd] = hWnd;
835     cs->m_cntOfHWnd ++;
836 
837     return TRUE; // continue enumeration (otherwise this would be false)
838 }
839 
840 BOOL CShellExt::LoadMenuIcon()
841 {
842 	char vimExeFile[BUFSIZE];
843 	getGvimName(vimExeFile, 1);
844 	if (vimExeFile[0] == '\0')
845 		return FALSE;
846 	HICON hVimIcon;
847 	if (ExtractIconEx(vimExeFile, 0, NULL, &hVimIcon, 1) == 0)
848 		return FALSE;
849 	m_hVimIconBitmap = IconToBitmap(hVimIcon,
850 		GetSysColorBrush(COLOR_MENU),
851 		GetSystemMetrics(SM_CXSMICON),
852 		GetSystemMetrics(SM_CYSMICON));
853 	return TRUE;
854 }
855 
856     static char *
857 searchpath(char *name)
858 {
859     static char widename[2 * BUFSIZE];
860     static char location[2 * BUFSIZE + 2];
861 
862     // There appears to be a bug in FindExecutableA() on Windows NT.
863     // Use FindExecutableW() instead...
864     MultiByteToWideChar(CP_ACP, 0, (LPCSTR)name, -1,
865 	    (LPWSTR)widename, BUFSIZE);
866     if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
867 		(LPWSTR)location) > (HINSTANCE)32)
868     {
869 	WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
870 		(LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
871 	return widename;
872     }
873     return (char *)"";
874 }
875 
876 STDMETHODIMP CShellExt::InvokeGvim(HWND hParent,
877 				   LPCSTR  /* pszWorkingDir */,
878 				   LPCSTR  /* pszCmd */,
879 				   LPCSTR  /* pszParam */,
880 				   int  /* iShowCmd */)
881 {
882     wchar_t m_szFileUserClickedOn[BUFSIZE];
883     wchar_t cmdStrW[BUFSIZE];
884     UINT i;
885 
886     for (i = 0; i < cbFiles; i++)
887     {
888 	DragQueryFileW((HDROP)medium.hGlobal,
889 		i,
890 		m_szFileUserClickedOn,
891 		sizeof(m_szFileUserClickedOn));
892 
893 	getGvimInvocationW(cmdStrW);
894 	wcscat(cmdStrW, L" \"");
895 
896 	if ((wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 2) < BUFSIZE)
897 	{
898 	    wcscat(cmdStrW, m_szFileUserClickedOn);
899 	    wcscat(cmdStrW, L"\"");
900 
901 	    STARTUPINFOW si;
902 	    PROCESS_INFORMATION pi;
903 
904 	    ZeroMemory(&si, sizeof(si));
905 	    si.cb = sizeof(si);
906 
907 	    // Start the child process.
908 	    if (!CreateProcessW(NULL,	// No module name (use command line).
909 			cmdStrW,	// Command line.
910 			NULL,		// Process handle not inheritable.
911 			NULL,		// Thread handle not inheritable.
912 			FALSE,		// Set handle inheritance to FALSE.
913 			0,		// No creation flags.
914 			NULL,		// Use parent's environment block.
915 			NULL,		// Use parent's starting directory.
916 			&si,		// Pointer to STARTUPINFO structure.
917 			&pi)		// Pointer to PROCESS_INFORMATION structure.
918 	       )
919 	    {
920 		MessageBox(
921 		    hParent,
922 		    _("Error creating process: Check if gvim is in your path!"),
923 		    _("gvimext.dll error"),
924 		    MB_OK);
925 	    }
926 	    else
927 	    {
928 		CloseHandle( pi.hProcess );
929 		CloseHandle( pi.hThread );
930 	    }
931 	}
932 	else
933 	{
934 	    MessageBox(
935 		hParent,
936 		_("Path length too long!"),
937 		_("gvimext.dll error"),
938 		MB_OK);
939 	}
940     }
941 
942     return NOERROR;
943 }
944 
945 
946 STDMETHODIMP CShellExt::InvokeSingleGvim(HWND hParent,
947 				   LPCSTR  /* pszWorkingDir */,
948 				   LPCSTR  /* pszCmd */,
949 				   LPCSTR  /* pszParam */,
950 				   int  /* iShowCmd */,
951 				   int useDiff)
952 {
953     wchar_t	m_szFileUserClickedOn[BUFSIZE];
954     wchar_t	*cmdStrW;
955     size_t	cmdlen;
956     size_t	len;
957     UINT i;
958 
959     cmdlen = BUFSIZE;
960     cmdStrW  = (wchar_t *) malloc(cmdlen * sizeof(wchar_t));
961     if (cmdStrW == NULL)
962 	return E_FAIL;
963     getGvimInvocationW(cmdStrW);
964 
965     if (useDiff)
966 	wcscat(cmdStrW, L" -d");
967     for (i = 0; i < cbFiles; i++)
968     {
969 	DragQueryFileW((HDROP)medium.hGlobal,
970 		i,
971 		m_szFileUserClickedOn,
972 		sizeof(m_szFileUserClickedOn));
973 
974 	len = wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 4;
975 	if (len > cmdlen)
976 	{
977 	    cmdlen = len + BUFSIZE;
978 	    wchar_t *cmdStrW_new = (wchar_t *)realloc(cmdStrW, cmdlen * sizeof(wchar_t));
979 	    if (cmdStrW_new == NULL)
980 	    {
981 		free(cmdStrW);
982 		return E_FAIL;
983 	    }
984 	    cmdStrW = cmdStrW_new;
985 	}
986 	wcscat(cmdStrW, L" \"");
987 	wcscat(cmdStrW, m_szFileUserClickedOn);
988 	wcscat(cmdStrW, L"\"");
989     }
990 
991     STARTUPINFOW si;
992     PROCESS_INFORMATION pi;
993 
994     ZeroMemory(&si, sizeof(si));
995     si.cb = sizeof(si);
996 
997     // Start the child process.
998     if (!CreateProcessW(NULL,	// No module name (use command line).
999 		cmdStrW,	// Command line.
1000 		NULL,		// Process handle not inheritable.
1001 		NULL,		// Thread handle not inheritable.
1002 		FALSE,		// Set handle inheritance to FALSE.
1003 		0,		// No creation flags.
1004 		NULL,		// Use parent's environment block.
1005 		NULL,		// Use parent's starting directory.
1006 		&si,		// Pointer to STARTUPINFO structure.
1007 		&pi)		// Pointer to PROCESS_INFORMATION structure.
1008        )
1009     {
1010 	MessageBox(
1011 	    hParent,
1012 	    _("Error creating process: Check if gvim is in your path!"),
1013 	    _("gvimext.dll error"),
1014 	    MB_OK);
1015     }
1016     else
1017     {
1018 	CloseHandle(pi.hProcess);
1019 	CloseHandle(pi.hThread);
1020     }
1021     free(cmdStrW);
1022 
1023     return NOERROR;
1024 }
1025