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