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