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 wchar_t *oldenv = NULL; 165 static HINSTANCE hLibintlDLL = 0; 166 static char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext; 167 static char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain; 168 static char *(*dyn_libintl_bindtextdomain)(const char *, const char *) 169 = null_libintl_bindtextdomain; 170 171 // 172 // Attempt to load libintl.dll. If it doesn't work, use dummy functions. 173 // "dir" is the directory where the libintl.dll might be. 174 // Return 1 for success, 0 for failure. 175 // 176 static int 177 dyn_libintl_init(char *dir) 178 { 179 int i; 180 static struct 181 { 182 char *name; 183 FARPROC *ptr; 184 } libintl_entry[] = 185 { 186 {(char *)"gettext", (FARPROC*)&dyn_libintl_gettext}, 187 {(char *)"textdomain", (FARPROC*)&dyn_libintl_textdomain}, 188 {(char *)"bindtextdomain", (FARPROC*)&dyn_libintl_bindtextdomain}, 189 {NULL, NULL} 190 }; 191 DWORD len, len2; 192 LPWSTR buf = NULL; 193 LPWSTR buf2 = NULL; 194 195 // No need to initialize twice. 196 if (hLibintlDLL) 197 return 1; 198 199 // Load gettext library from $VIMRUNTIME\GvimExt{64,32} directory. 200 // Add the directory to $PATH temporarily. 201 len = GetEnvironmentVariableW(L"PATH", NULL, 0); 202 len2 = MAX_PATH + 1 + len; 203 buf = (LPWSTR)malloc(len * sizeof(WCHAR)); 204 buf2 = (LPWSTR)malloc(len2 * sizeof(WCHAR)); 205 if (buf != NULL && buf2 != NULL) 206 { 207 GetEnvironmentVariableW(L"PATH", buf, len); 208 #ifdef _WIN64 209 _snwprintf(buf2, len2, L"%S\\GvimExt64;%s", dir, buf); 210 #else 211 _snwprintf(buf2, len2, L"%S\\GvimExt32;%s", dir, buf); 212 #endif 213 SetEnvironmentVariableW(L"PATH", buf2); 214 hLibintlDLL = LoadLibrary(GETTEXT_DLL); 215 #ifdef GETTEXT_DLL_ALT 216 if (!hLibintlDLL) 217 hLibintlDLL = LoadLibrary(GETTEXT_DLL_ALT); 218 #endif 219 SetEnvironmentVariableW(L"PATH", buf); 220 } 221 free(buf); 222 free(buf2); 223 if (!hLibintlDLL) 224 return 0; 225 226 // Get the addresses of the functions we need. 227 for (i = 0; libintl_entry[i].name != NULL 228 && libintl_entry[i].ptr != NULL; ++i) 229 { 230 if ((*libintl_entry[i].ptr = GetProcAddress(hLibintlDLL, 231 libintl_entry[i].name)) == NULL) 232 { 233 dyn_libintl_end(); 234 return 0; 235 } 236 } 237 return 1; 238 } 239 240 static void 241 dyn_libintl_end(void) 242 { 243 if (hLibintlDLL) 244 FreeLibrary(hLibintlDLL); 245 hLibintlDLL = NULL; 246 dyn_libintl_gettext = null_libintl_gettext; 247 dyn_libintl_textdomain = null_libintl_textdomain; 248 dyn_libintl_bindtextdomain = null_libintl_bindtextdomain; 249 } 250 251 static char * 252 null_libintl_gettext(const char *msgid) 253 { 254 return (char *)msgid; 255 } 256 257 static char * 258 null_libintl_bindtextdomain(const char * /* domainname */, const char * /* dirname */) 259 { 260 return NULL; 261 } 262 263 static char * 264 null_libintl_textdomain(const char* /* domainname */) 265 { 266 return NULL; 267 } 268 269 // 270 // Setup for translating strings. 271 // 272 static void 273 dyn_gettext_load(void) 274 { 275 char szBuff[BUFSIZE]; 276 char szLang[BUFSIZE]; 277 DWORD len; 278 HKEY keyhandle; 279 int gotlang = 0; 280 281 strcpy(szLang, "LANG="); 282 283 // First try getting the language from the registry, this can be 284 // used to overrule the system language. 285 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0, 286 KEY_READ, &keyhandle) == ERROR_SUCCESS) 287 { 288 len = BUFSIZE; 289 if (RegQueryValueEx(keyhandle, "lang", 0, NULL, (BYTE*)szBuff, &len) 290 == ERROR_SUCCESS) 291 { 292 szBuff[len] = 0; 293 strcat(szLang, szBuff); 294 gotlang = 1; 295 } 296 RegCloseKey(keyhandle); 297 } 298 299 if (!gotlang && getenv("LANG") == NULL) 300 { 301 // Get the language from the system. 302 // Could use LOCALE_SISO639LANGNAME, but it's not in Win95. 303 // LOCALE_SABBREVLANGNAME gives us three letters, like "enu", we use 304 // only the first two. 305 len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, 306 (LPTSTR)szBuff, BUFSIZE); 307 if (len >= 2 && _strnicmp(szBuff, "en", 2) != 0) 308 { 309 // There are a few exceptions (probably more) 310 if (_strnicmp(szBuff, "cht", 3) == 0 311 || _strnicmp(szBuff, "zht", 3) == 0) 312 strcpy(szBuff, "zh_TW"); 313 else if (_strnicmp(szBuff, "chs", 3) == 0 314 || _strnicmp(szBuff, "zhc", 3) == 0) 315 strcpy(szBuff, "zh_CN"); 316 else if (_strnicmp(szBuff, "jp", 2) == 0) 317 strcpy(szBuff, "ja"); 318 else 319 szBuff[2] = 0; // truncate to two-letter code 320 strcat(szLang, szBuff); 321 gotlang = 1; 322 } 323 } 324 if (gotlang) 325 putenv(szLang); 326 327 // Try to locate the runtime files. The path is used to find libintl.dll 328 // and the vim.mo files. 329 getRuntimeDir(szBuff); 330 if (szBuff[0] != 0) 331 { 332 len = (DWORD)strlen(szBuff); 333 if (dyn_libintl_init(szBuff)) 334 { 335 strcpy(szBuff + len, "lang"); 336 337 (*dyn_libintl_bindtextdomain)(VIMPACKAGE, szBuff); 338 (*dyn_libintl_textdomain)(VIMPACKAGE); 339 } 340 } 341 } 342 343 static void 344 dyn_gettext_free(void) 345 { 346 dyn_libintl_end(); 347 } 348 #endif // FEAT_GETTEXT 349 350 // 351 // Global variables 352 // 353 UINT g_cRefThisDll = 0; // Reference count of this DLL. 354 HINSTANCE g_hmodThisDll = NULL; // Handle to this DLL itself. 355 356 357 //--------------------------------------------------------------------------- 358 // DllMain 359 //--------------------------------------------------------------------------- 360 extern "C" int APIENTRY 361 DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /* lpReserved */) 362 { 363 switch (dwReason) 364 { 365 case DLL_PROCESS_ATTACH: 366 // Extension DLL one-time initialization 367 g_hmodThisDll = hInstance; 368 break; 369 370 case DLL_PROCESS_DETACH: 371 break; 372 } 373 374 return 1; // ok 375 } 376 377 static void 378 inc_cRefThisDLL() 379 { 380 #ifdef FEAT_GETTEXT 381 if (g_cRefThisDll == 0) { 382 dyn_gettext_load(); 383 oldenv = GetEnvironmentStringsW(); 384 } 385 #endif 386 InterlockedIncrement((LPLONG)&g_cRefThisDll); 387 } 388 389 static void 390 dec_cRefThisDLL() 391 { 392 #ifdef FEAT_GETTEXT 393 if (InterlockedDecrement((LPLONG)&g_cRefThisDll) == 0) { 394 dyn_gettext_free(); 395 if (oldenv != NULL) { 396 FreeEnvironmentStringsW(oldenv); 397 oldenv = NULL; 398 } 399 } 400 #else 401 InterlockedDecrement((LPLONG)&g_cRefThisDll); 402 #endif 403 } 404 405 //--------------------------------------------------------------------------- 406 // DllCanUnloadNow 407 //--------------------------------------------------------------------------- 408 409 STDAPI DllCanUnloadNow(void) 410 { 411 return (g_cRefThisDll == 0 ? S_OK : S_FALSE); 412 } 413 414 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut) 415 { 416 *ppvOut = NULL; 417 418 if (IsEqualIID(rclsid, CLSID_ShellExtension)) 419 { 420 CShellExtClassFactory *pcf = new CShellExtClassFactory; 421 422 return pcf->QueryInterface(riid, ppvOut); 423 } 424 425 return CLASS_E_CLASSNOTAVAILABLE; 426 } 427 428 CShellExtClassFactory::CShellExtClassFactory() 429 { 430 m_cRef = 0L; 431 432 inc_cRefThisDLL(); 433 } 434 435 CShellExtClassFactory::~CShellExtClassFactory() 436 { 437 dec_cRefThisDLL(); 438 } 439 440 STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid, 441 LPVOID FAR *ppv) 442 { 443 *ppv = NULL; 444 445 // any interface on this object is the object pointer 446 447 if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)) 448 { 449 *ppv = (LPCLASSFACTORY)this; 450 451 AddRef(); 452 453 return NOERROR; 454 } 455 456 return E_NOINTERFACE; 457 } 458 459 STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef() 460 { 461 return InterlockedIncrement((LPLONG)&m_cRef); 462 } 463 464 STDMETHODIMP_(ULONG) CShellExtClassFactory::Release() 465 { 466 if (InterlockedDecrement((LPLONG)&m_cRef)) 467 return m_cRef; 468 469 delete this; 470 471 return 0L; 472 } 473 474 STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, 475 REFIID riid, 476 LPVOID *ppvObj) 477 { 478 *ppvObj = NULL; 479 480 // Shell extensions typically don't support aggregation (inheritance) 481 482 if (pUnkOuter) 483 return CLASS_E_NOAGGREGATION; 484 485 // Create the main shell extension object. The shell will then call 486 // QueryInterface with IID_IShellExtInit--this is how shell extensions are 487 // initialized. 488 489 LPCSHELLEXT pShellExt = new CShellExt(); // create the CShellExt object 490 491 if (NULL == pShellExt) 492 return E_OUTOFMEMORY; 493 494 return pShellExt->QueryInterface(riid, ppvObj); 495 } 496 497 498 STDMETHODIMP CShellExtClassFactory::LockServer(BOOL /* fLock */) 499 { 500 return NOERROR; 501 } 502 503 // *********************** CShellExt ************************* 504 CShellExt::CShellExt() 505 { 506 m_cRef = 0L; 507 m_pDataObj = NULL; 508 509 inc_cRefThisDLL(); 510 511 LoadMenuIcon(); 512 } 513 514 CShellExt::~CShellExt() 515 { 516 if (m_pDataObj) 517 m_pDataObj->Release(); 518 519 dec_cRefThisDLL(); 520 521 if (m_hVimIconBitmap) 522 DeleteObject(m_hVimIconBitmap); 523 } 524 525 STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv) 526 { 527 *ppv = NULL; 528 529 if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown)) 530 { 531 *ppv = (LPSHELLEXTINIT)this; 532 } 533 else if (IsEqualIID(riid, IID_IContextMenu)) 534 { 535 *ppv = (LPCONTEXTMENU)this; 536 } 537 538 if (*ppv) 539 { 540 AddRef(); 541 542 return NOERROR; 543 } 544 545 return E_NOINTERFACE; 546 } 547 548 STDMETHODIMP_(ULONG) CShellExt::AddRef() 549 { 550 return InterlockedIncrement((LPLONG)&m_cRef); 551 } 552 553 STDMETHODIMP_(ULONG) CShellExt::Release() 554 { 555 556 if (InterlockedDecrement((LPLONG)&m_cRef)) 557 return m_cRef; 558 559 delete this; 560 561 return 0L; 562 } 563 564 565 // 566 // FUNCTION: CShellExt::Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY) 567 // 568 // PURPOSE: Called by the shell when initializing a context menu or property 569 // sheet extension. 570 // 571 // PARAMETERS: 572 // pIDFolder - Specifies the parent folder 573 // pDataObj - Specifies the set of items selected in that folder. 574 // hRegKey - Specifies the type of the focused item in the selection. 575 // 576 // RETURN VALUE: 577 // 578 // NOERROR in all cases. 579 // 580 // COMMENTS: Note that at the time this function is called, we don't know 581 // (or care) what type of shell extension is being initialized. 582 // It could be a context menu or a property sheet. 583 // 584 585 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST /* pIDFolder */, 586 LPDATAOBJECT pDataObj, 587 HKEY /* hRegKey */) 588 { 589 // Initialize can be called more than once 590 if (m_pDataObj) 591 m_pDataObj->Release(); 592 593 // duplicate the object pointer and registry handle 594 595 if (pDataObj) 596 { 597 m_pDataObj = pDataObj; 598 pDataObj->AddRef(); 599 } 600 601 return NOERROR; 602 } 603 604 605 // 606 // FUNCTION: CShellExt::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT) 607 // 608 // PURPOSE: Called by the shell just before the context menu is displayed. 609 // This is where you add your specific menu items. 610 // 611 // PARAMETERS: 612 // hMenu - Handle to the context menu 613 // indexMenu - Index of where to begin inserting menu items 614 // idCmdFirst - Lowest value for new menu ID's 615 // idCmtLast - Highest value for new menu ID's 616 // uFlags - Specifies the context of the menu event 617 // 618 // RETURN VALUE: 619 // 620 // 621 // COMMENTS: 622 // 623 624 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu, 625 UINT indexMenu, 626 UINT idCmdFirst, 627 UINT /* idCmdLast */, 628 UINT /* uFlags */) 629 { 630 UINT idCmd = idCmdFirst; 631 632 hres = m_pDataObj->GetData(&fmte, &medium); 633 if (medium.hGlobal) 634 cbFiles = DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, 0, 0); 635 636 // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); 637 638 // Initialize m_cntOfHWnd to 0 639 m_cntOfHWnd = 0; 640 641 HKEY keyhandle; 642 bool showExisting = true; 643 bool showIcons = true; 644 645 // Check whether "Edit with existing Vim" entries are disabled. 646 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0, 647 KEY_READ, &keyhandle) == ERROR_SUCCESS) 648 { 649 if (RegQueryValueEx(keyhandle, "DisableEditWithExisting", 0, NULL, 650 NULL, NULL) == ERROR_SUCCESS) 651 showExisting = false; 652 if (RegQueryValueEx(keyhandle, "DisableContextMenuIcons", 0, NULL, 653 NULL, NULL) == ERROR_SUCCESS) 654 showIcons = false; 655 RegCloseKey(keyhandle); 656 } 657 658 // Retrieve all the vim instances, unless disabled. 659 if (showExisting) 660 EnumWindows(EnumWindowsProc, (LPARAM)this); 661 662 MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; 663 mii.fMask = MIIM_STRING | MIIM_ID; 664 if (showIcons) 665 { 666 mii.fMask |= MIIM_BITMAP; 667 mii.hbmpItem = m_hVimIconBitmap; 668 } 669 670 if (cbFiles > 1) 671 { 672 mii.wID = idCmd++; 673 mii.dwTypeData = _("Edit with &multiple Vims"); 674 mii.cch = lstrlen(mii.dwTypeData); 675 InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); 676 677 mii.wID = idCmd++; 678 mii.dwTypeData = _("Edit with single &Vim"); 679 mii.cch = lstrlen(mii.dwTypeData); 680 InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); 681 682 if (cbFiles <= 4) 683 { 684 // Can edit up to 4 files in diff mode 685 mii.wID = idCmd++; 686 mii.dwTypeData = _("Diff with Vim"); 687 mii.cch = lstrlen(mii.dwTypeData); 688 InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); 689 m_edit_existing_off = 3; 690 } 691 else 692 m_edit_existing_off = 2; 693 694 } 695 else 696 { 697 mii.wID = idCmd++; 698 mii.dwTypeData = _("Edit with &Vim"); 699 mii.cch = lstrlen(mii.dwTypeData); 700 InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); 701 m_edit_existing_off = 1; 702 } 703 704 HMENU hSubMenu = NULL; 705 if (m_cntOfHWnd > 1) 706 { 707 hSubMenu = CreatePopupMenu(); 708 mii.fMask |= MIIM_SUBMENU; 709 mii.wID = idCmd; 710 mii.dwTypeData = _("Edit with existing Vim"); 711 mii.cch = lstrlen(mii.dwTypeData); 712 mii.hSubMenu = hSubMenu; 713 InsertMenuItem(hMenu, indexMenu++, TRUE, &mii); 714 mii.fMask = mii.fMask & ~MIIM_SUBMENU; 715 mii.hSubMenu = NULL; 716 } 717 // Now display all the vim instances 718 for (int i = 0; i < m_cntOfHWnd; i++) 719 { 720 char title[BUFSIZE]; 721 char temp[BUFSIZE]; 722 int index; 723 HMENU hmenu; 724 725 // Obtain window title, continue if can not 726 if (GetWindowText(m_hWnd[i], title, BUFSIZE - 1) == 0) 727 continue; 728 // Truncate the title before the path, keep the file name 729 char *pos = strchr(title, '('); 730 if (pos != NULL) 731 { 732 if (pos > title && pos[-1] == ' ') 733 --pos; 734 *pos = 0; 735 } 736 // Now concatenate 737 if (m_cntOfHWnd > 1) 738 temp[0] = '\0'; 739 else 740 { 741 strncpy(temp, _("Edit with existing Vim - "), BUFSIZE - 1); 742 temp[BUFSIZE - 1] = '\0'; 743 } 744 strncat(temp, title, BUFSIZE - 1 - strlen(temp)); 745 temp[BUFSIZE - 1] = '\0'; 746 747 mii.wID = idCmd++; 748 mii.dwTypeData = temp; 749 mii.cch = lstrlen(mii.dwTypeData); 750 if (m_cntOfHWnd > 1) 751 { 752 hmenu = hSubMenu; 753 index = i; 754 } 755 else 756 { 757 hmenu = hMenu; 758 index = indexMenu++; 759 } 760 InsertMenuItem(hmenu, index, TRUE, &mii); 761 } 762 // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL); 763 764 // Must return number of menu items we added. 765 return ResultFromShort(idCmd-idCmdFirst); 766 } 767 768 // 769 // FUNCTION: CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO) 770 // 771 // PURPOSE: Called by the shell after the user has selected on of the 772 // menu items that was added in QueryContextMenu(). 773 // 774 // PARAMETERS: 775 // lpcmi - Pointer to an CMINVOKECOMMANDINFO structure 776 // 777 // RETURN VALUE: 778 // 779 // 780 // COMMENTS: 781 // 782 783 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) 784 { 785 HRESULT hr = E_INVALIDARG; 786 787 // If HIWORD(lpcmi->lpVerb) then we have been called programmatically 788 // and lpVerb is a command that should be invoked. Otherwise, the shell 789 // has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has 790 // selected. Actually, it's (menu ID - idCmdFirst) from QueryContextMenu(). 791 if (!HIWORD(lpcmi->lpVerb)) 792 { 793 UINT idCmd = LOWORD(lpcmi->lpVerb); 794 795 if (idCmd >= m_edit_existing_off) 796 { 797 // Existing with vim instance 798 hr = PushToWindow(lpcmi->hwnd, 799 lpcmi->lpDirectory, 800 lpcmi->lpVerb, 801 lpcmi->lpParameters, 802 lpcmi->nShow, 803 idCmd - m_edit_existing_off); 804 } 805 else 806 { 807 switch (idCmd) 808 { 809 case 0: 810 hr = InvokeGvim(lpcmi->hwnd, 811 lpcmi->lpDirectory, 812 lpcmi->lpVerb, 813 lpcmi->lpParameters, 814 lpcmi->nShow); 815 break; 816 case 1: 817 hr = InvokeSingleGvim(lpcmi->hwnd, 818 lpcmi->lpDirectory, 819 lpcmi->lpVerb, 820 lpcmi->lpParameters, 821 lpcmi->nShow, 822 0); 823 break; 824 case 2: 825 hr = InvokeSingleGvim(lpcmi->hwnd, 826 lpcmi->lpDirectory, 827 lpcmi->lpVerb, 828 lpcmi->lpParameters, 829 lpcmi->nShow, 830 1); 831 break; 832 } 833 } 834 } 835 return hr; 836 } 837 838 STDMETHODIMP CShellExt::PushToWindow(HWND /* hParent */, 839 LPCSTR /* pszWorkingDir */, 840 LPCSTR /* pszCmd */, 841 LPCSTR /* pszParam */, 842 int /* iShowCmd */, 843 int idHWnd) 844 { 845 HWND hWnd = m_hWnd[idHWnd]; 846 847 // Show and bring vim instance to foreground 848 if (IsIconic(hWnd) != 0) 849 ShowWindow(hWnd, SW_RESTORE); 850 else 851 ShowWindow(hWnd, SW_SHOW); 852 SetForegroundWindow(hWnd); 853 854 // Post the selected files to the vim instance 855 PostMessage(hWnd, WM_DROPFILES, (WPARAM)medium.hGlobal, 0); 856 857 return NOERROR; 858 } 859 860 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR /* idCmd */, 861 UINT uFlags, 862 UINT FAR * /* reserved */, 863 LPSTR pszName, 864 UINT cchMax) 865 { 866 if (uFlags == GCS_HELPTEXT && cchMax > 35) 867 lstrcpy(pszName, _("Edits the selected file(s) with Vim")); 868 869 return NOERROR; 870 } 871 872 BOOL CALLBACK CShellExt::EnumWindowsProc(HWND hWnd, LPARAM lParam) 873 { 874 char temp[BUFSIZE]; 875 876 // First do a bunch of check 877 // No invisible window 878 if (!IsWindowVisible(hWnd)) return TRUE; 879 // No child window ??? 880 // if (GetParent(hWnd)) return TRUE; 881 // Class name should be Vim, if failed to get class name, return 882 if (GetClassName(hWnd, temp, sizeof(temp)) == 0) 883 return TRUE; 884 // Compare class name to that of vim, if not, return 885 if (_strnicmp(temp, "vim", sizeof("vim")) != 0) 886 return TRUE; 887 // First check if the number of vim instance exceeds MAX_HWND 888 CShellExt *cs = (CShellExt*) lParam; 889 if (cs->m_cntOfHWnd >= MAX_HWND) return TRUE; 890 // Now we get the vim window, put it into some kind of array 891 cs->m_hWnd[cs->m_cntOfHWnd] = hWnd; 892 cs->m_cntOfHWnd ++; 893 894 return TRUE; // continue enumeration (otherwise this would be false) 895 } 896 897 BOOL CShellExt::LoadMenuIcon() 898 { 899 char vimExeFile[BUFSIZE]; 900 getGvimName(vimExeFile, 1); 901 if (vimExeFile[0] == '\0') 902 return FALSE; 903 HICON hVimIcon; 904 if (ExtractIconEx(vimExeFile, 0, NULL, &hVimIcon, 1) == 0) 905 return FALSE; 906 m_hVimIconBitmap = IconToBitmap(hVimIcon, 907 GetSysColorBrush(COLOR_MENU), 908 GetSystemMetrics(SM_CXSMICON), 909 GetSystemMetrics(SM_CYSMICON)); 910 return TRUE; 911 } 912 913 static char * 914 searchpath(char *name) 915 { 916 static char widename[2 * BUFSIZE]; 917 static char location[2 * BUFSIZE + 2]; 918 919 // There appears to be a bug in FindExecutableA() on Windows NT. 920 // Use FindExecutableW() instead... 921 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)name, -1, 922 (LPWSTR)widename, BUFSIZE); 923 if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"", 924 (LPWSTR)location) > (HINSTANCE)32) 925 { 926 WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1, 927 (LPSTR)widename, 2 * BUFSIZE, NULL, NULL); 928 return widename; 929 } 930 return (char *)""; 931 } 932 933 STDMETHODIMP CShellExt::InvokeGvim(HWND hParent, 934 LPCSTR /* pszWorkingDir */, 935 LPCSTR /* pszCmd */, 936 LPCSTR /* pszParam */, 937 int /* iShowCmd */) 938 { 939 wchar_t m_szFileUserClickedOn[BUFSIZE]; 940 wchar_t cmdStrW[BUFSIZE]; 941 UINT i; 942 943 for (i = 0; i < cbFiles; i++) 944 { 945 DragQueryFileW((HDROP)medium.hGlobal, 946 i, 947 m_szFileUserClickedOn, 948 sizeof(m_szFileUserClickedOn)); 949 950 getGvimInvocationW(cmdStrW); 951 wcscat(cmdStrW, L" \""); 952 953 if ((wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 2) < BUFSIZE) 954 { 955 wcscat(cmdStrW, m_szFileUserClickedOn); 956 wcscat(cmdStrW, L"\""); 957 958 STARTUPINFOW si; 959 PROCESS_INFORMATION pi; 960 961 ZeroMemory(&si, sizeof(si)); 962 si.cb = sizeof(si); 963 964 // Start the child process. 965 if (!CreateProcessW(NULL, // No module name (use command line). 966 cmdStrW, // Command line. 967 NULL, // Process handle not inheritable. 968 NULL, // Thread handle not inheritable. 969 FALSE, // Set handle inheritance to FALSE. 970 oldenv == NULL ? 0 : CREATE_UNICODE_ENVIRONMENT, 971 oldenv, // Use unmodified environment block. 972 NULL, // Use parent's starting directory. 973 &si, // Pointer to STARTUPINFO structure. 974 &pi) // Pointer to PROCESS_INFORMATION structure. 975 ) 976 { 977 MessageBox( 978 hParent, 979 _("Error creating process: Check if gvim is in your path!"), 980 _("gvimext.dll error"), 981 MB_OK); 982 } 983 else 984 { 985 CloseHandle( pi.hProcess ); 986 CloseHandle( pi.hThread ); 987 } 988 } 989 else 990 { 991 MessageBox( 992 hParent, 993 _("Path length too long!"), 994 _("gvimext.dll error"), 995 MB_OK); 996 } 997 } 998 999 return NOERROR; 1000 } 1001 1002 1003 STDMETHODIMP CShellExt::InvokeSingleGvim(HWND hParent, 1004 LPCSTR /* pszWorkingDir */, 1005 LPCSTR /* pszCmd */, 1006 LPCSTR /* pszParam */, 1007 int /* iShowCmd */, 1008 int useDiff) 1009 { 1010 wchar_t m_szFileUserClickedOn[BUFSIZE]; 1011 wchar_t *cmdStrW; 1012 size_t cmdlen; 1013 size_t len; 1014 UINT i; 1015 1016 cmdlen = BUFSIZE; 1017 cmdStrW = (wchar_t *) malloc(cmdlen * sizeof(wchar_t)); 1018 if (cmdStrW == NULL) 1019 return E_FAIL; 1020 getGvimInvocationW(cmdStrW); 1021 1022 if (useDiff) 1023 wcscat(cmdStrW, L" -d"); 1024 for (i = 0; i < cbFiles; i++) 1025 { 1026 DragQueryFileW((HDROP)medium.hGlobal, 1027 i, 1028 m_szFileUserClickedOn, 1029 sizeof(m_szFileUserClickedOn)); 1030 1031 len = wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 4; 1032 if (len > cmdlen) 1033 { 1034 cmdlen = len + BUFSIZE; 1035 wchar_t *cmdStrW_new = (wchar_t *)realloc(cmdStrW, cmdlen * sizeof(wchar_t)); 1036 if (cmdStrW_new == NULL) 1037 { 1038 free(cmdStrW); 1039 return E_FAIL; 1040 } 1041 cmdStrW = cmdStrW_new; 1042 } 1043 wcscat(cmdStrW, L" \""); 1044 wcscat(cmdStrW, m_szFileUserClickedOn); 1045 wcscat(cmdStrW, L"\""); 1046 } 1047 1048 STARTUPINFOW si; 1049 PROCESS_INFORMATION pi; 1050 1051 ZeroMemory(&si, sizeof(si)); 1052 si.cb = sizeof(si); 1053 1054 // Start the child process. 1055 if (!CreateProcessW(NULL, // No module name (use command line). 1056 cmdStrW, // Command line. 1057 NULL, // Process handle not inheritable. 1058 NULL, // Thread handle not inheritable. 1059 FALSE, // Set handle inheritance to FALSE. 1060 oldenv == NULL ? 0 : CREATE_UNICODE_ENVIRONMENT, 1061 oldenv, // Use unmodified environment block. 1062 NULL, // Use parent's starting directory. 1063 &si, // Pointer to STARTUPINFO structure. 1064 &pi) // Pointer to PROCESS_INFORMATION structure. 1065 ) 1066 { 1067 MessageBox( 1068 hParent, 1069 _("Error creating process: Check if gvim is in your path!"), 1070 _("gvimext.dll error"), 1071 MB_OK); 1072 } 1073 else 1074 { 1075 CloseHandle(pi.hProcess); 1076 CloseHandle(pi.hThread); 1077 } 1078 free(cmdStrW); 1079 1080 return NOERROR; 1081 } 1082