Use StartsWith to reduce magic numbers in code
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blob9909ef21ad148490448666c7b7bb92c9ed7a3776
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016 - TortoiseSVN
4 // Copyright (C) 2008-2016 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "ShellExt.h"
22 #include "ItemIDList.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
25 #include "Git.h"
26 #include "GitStatus.h"
27 #include "TGitPath.h"
28 #include "PathUtils.h"
29 #include "CreateProcessHelper.h"
30 #include "FormatMessageWrapper.h"
31 #include "..\TGitCache\CacheInterface.h"
32 #include "resource.h"
34 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
35 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
37 int g_shellidlist=RegisterClipboardFormat(CFSTR_SHELLIDLIST);
39 extern MenuInfo menuInfo[];
40 static int g_syncSeq = 0;
42 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder,
43 LPDATAOBJECT pDataObj,
44 HKEY hRegKey)
46 __try
48 return Initialize_Wrap(pIDFolder, pDataObj, hRegKey);
50 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
53 return E_FAIL;
56 STDMETHODIMP CShellExt::Initialize_Wrap(LPCITEMIDLIST pIDFolder,
57 LPDATAOBJECT pDataObj,
58 HKEY /* hRegKey */)
60 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: Initialize\n");
61 PreserveChdir preserveChdir;
62 files_.clear();
63 folder_.clear();
64 uuidSource.clear();
65 uuidTarget.clear();
66 itemStates = 0;
67 itemStatesFolder = 0;
68 std::wstring statuspath;
69 git_wc_status_kind fetchedstatus = git_wc_status_none;
70 // get selected files/folders
71 if (pDataObj)
73 STGMEDIUM medium;
74 FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
75 nullptr,
76 DVASPECT_CONTENT,
77 -1,
78 TYMED_HGLOBAL};
79 HRESULT hres = pDataObj->GetData(&fmte, &medium);
81 if (SUCCEEDED(hres) && medium.hGlobal)
83 if (m_State == FileStateDropHandler)
85 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
87 ReleaseStgMedium(&medium);
88 return S_OK;
91 FORMATETC etc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
92 STGMEDIUM stg = { TYMED_HGLOBAL };
93 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
95 ReleaseStgMedium ( &medium );
96 return E_INVALIDARG;
100 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
101 if (!drop)
103 ReleaseStgMedium ( &stg );
104 ReleaseStgMedium ( &medium );
105 return E_INVALIDARG;
108 int count = DragQueryFile(drop, (UINT)-1, nullptr, 0);
109 if (count == 1)
110 itemStates |= ITEMIS_ONLYONE;
111 for (int i = 0; i < count; i++)
113 // find the path length in chars
114 UINT len = DragQueryFile(drop, i, nullptr, 0);
115 if (len == 0)
116 continue;
117 auto szFileName = std::make_unique<TCHAR[]>(len + 1);
118 if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
119 continue;
120 auto str = std::wstring(szFileName.get());
121 if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
124 CTGitPath strpath;
125 strpath.SetFromWin(str.c_str());
126 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
127 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
129 files_.push_back(str);
130 if (i == 0)
132 //get the git status of the item
133 git_wc_status_kind status = git_wc_status_none;
134 CTGitPath askedpath;
135 askedpath.SetFromWin(str.c_str());
136 CString workTreePath;
137 askedpath.HasAdminDir(&workTreePath);
138 uuidSource = workTreePath;
141 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
143 CTGitPath tpath(str.c_str());
144 if (!tpath.HasAdminDir())
146 status = git_wc_status_none;
147 continue;
149 if (tpath.IsAdminDir())
151 status = git_wc_status_none;
152 continue;
154 TGITCacheResponse itemStatus;
155 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
156 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
158 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
159 if (askedpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
161 itemStates |= ITEMIS_FOLDER;
162 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
163 itemStates |= ITEMIS_FOLDERINGIT;
167 else
169 GitStatus stat;
170 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
171 if (stat.status)
173 statuspath = str;
174 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
175 fetchedstatus = status;
176 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
178 itemStates |= ITEMIS_FOLDER;
179 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
180 itemStates |= ITEMIS_FOLDERINGIT;
182 //if ((stat.status->entry)&&(stat.status->entry->uuid))
183 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
185 else
187 // sometimes, git_client_status() returns with an error.
188 // in that case, we have to check if the working copy is versioned
189 // anyway to show the 'correct' context menu
190 if (askedpath.HasAdminDir())
191 status = git_wc_status_normal;
195 catch ( ... )
197 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
200 // TODO: should we really assume any sub-directory to be versioned
201 // or only if it contains versioned files
202 itemStates |= askedpath.GetAdminDirMask();
204 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
205 itemStates &= ~ITEMIS_INGIT;
207 if (status == git_wc_status_ignored)
208 itemStates |= ITEMIS_IGNORED;
209 if (status == git_wc_status_normal)
210 itemStates |= ITEMIS_NORMAL;
211 if (status == git_wc_status_conflicted)
212 itemStates |= ITEMIS_CONFLICTED;
213 if (status == git_wc_status_added)
214 itemStates |= ITEMIS_ADDED;
215 if (status == git_wc_status_deleted)
216 itemStates |= ITEMIS_DELETED;
219 } // for (int i = 0; i < count; i++)
220 GlobalUnlock ( drop );
221 ReleaseStgMedium ( &stg );
223 } // if (m_State == FileStateDropHandler)
224 else
226 //Enumerate PIDLs which the user has selected
227 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
228 ItemIDList parent( GetPIDLFolder (cida));
230 int count = cida->cidl;
231 BOOL statfetched = FALSE;
232 for (int i = 0; i < count; ++i)
234 ItemIDList child (GetPIDLItem (cida, i), &parent);
235 std::wstring str = child.toString();
236 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
238 //check if our menu is requested for a git admin directory
239 if (GitAdminDir::IsAdminDirPath(str.c_str()))
240 continue;
242 files_.push_back(str);
243 CTGitPath strpath;
244 strpath.SetFromWin(str.c_str());
245 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
246 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
247 if (!statfetched)
249 //get the git status of the item
250 git_wc_status_kind status = git_wc_status_none;
251 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
253 if (strpath.HasAdminDir())
254 status = git_wc_status_normal;
256 else
260 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
262 CTGitPath tpath(str.c_str());
263 if(!tpath.HasAdminDir())
265 status = git_wc_status_none;
266 continue;
268 if(tpath.IsAdminDir())
270 status = git_wc_status_none;
271 continue;
273 TGITCacheResponse itemStatus;
274 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
275 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
277 fetchedstatus = status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
278 if (strpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
280 itemStates |= ITEMIS_FOLDER;
281 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
282 itemStates |= ITEMIS_FOLDERINGIT;
284 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
285 itemStates |= ITEMIS_CONFLICTED;
288 else
290 GitStatus stat;
291 if (strpath.HasAdminDir())
292 stat.GetStatus(strpath, false, false, true);
293 statuspath = str;
294 if (stat.status)
296 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
297 fetchedstatus = status;
298 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
300 itemStates |= ITEMIS_FOLDER;
301 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
302 itemStates |= ITEMIS_FOLDERINGIT;
304 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
305 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
306 itemStates |= ITEMIS_CONFLICTED;
307 //if ((stat.status->entry)&&(stat.status->entry->uuid))
308 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
310 else
312 // sometimes, git_client_status() returns with an error.
313 // in that case, we have to check if the working copy is versioned
314 // anyway to show the 'correct' context menu
315 if (strpath.HasAdminDir())
317 status = git_wc_status_normal;
318 fetchedstatus = status;
322 statfetched = TRUE;
324 catch ( ... )
326 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
330 itemStates |= strpath.GetAdminDirMask();
332 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
333 itemStates &= ~ITEMIS_INGIT;
334 if (status == git_wc_status_ignored)
336 itemStates |= ITEMIS_IGNORED;
339 if (status == git_wc_status_normal)
340 itemStates |= ITEMIS_NORMAL;
341 if (status == git_wc_status_conflicted)
342 itemStates |= ITEMIS_CONFLICTED;
343 if (status == git_wc_status_added)
344 itemStates |= ITEMIS_ADDED;
345 if (status == git_wc_status_deleted)
346 itemStates |= ITEMIS_DELETED;
349 } // for (int i = 0; i < count; ++i)
350 ItemIDList child (GetPIDLItem (cida, 0), &parent);
351 if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
352 itemStates |= ITEMIS_INVERSIONEDFOLDER;
354 if (GitAdminDir::IsBareRepo(child.toString().c_str()))
355 itemStates = ITEMIS_BAREREPO;
357 GlobalUnlock(medium.hGlobal);
359 // if the item is a versioned folder, check if there's a patch file
360 // in the clipboard to be used in "Apply Patch"
361 UINT cFormatDiff = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
362 if (cFormatDiff)
364 if (IsClipboardFormatAvailable(cFormatDiff))
365 itemStates |= ITEMIS_PATCHINCLIPBOARD;
367 if (IsClipboardFormatAvailable(CF_HDROP))
368 itemStates |= ITEMIS_PATHINCLIPBOARD;
372 ReleaseStgMedium ( &medium );
373 if (medium.pUnkForRelease)
375 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
376 relInterface->Release();
381 // get folder background
382 if (pIDFolder)
384 ItemIDList list(pIDFolder);
385 folder_ = list.toString();
386 git_wc_status_kind status = git_wc_status_none;
387 if (IsClipboardFormatAvailable(CF_HDROP))
388 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
390 CTGitPath askedpath;
391 askedpath.SetFromWin(folder_.c_str());
393 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
395 if (folder_.compare(statuspath)!=0)
397 CString worktreePath;
398 askedpath.HasAdminDir(&worktreePath);
399 uuidTarget = worktreePath;
402 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
404 CTGitPath tpath(folder_.c_str());
405 if(!tpath.HasAdminDir())
406 status = git_wc_status_none;
407 else if(tpath.IsAdminDir())
408 status = git_wc_status_none;
409 else
411 TGITCacheResponse itemStatus;
412 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
413 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
414 status = GitStatus::GetMoreImportant(itemStatus.m_status.text_status, itemStatus.m_status.prop_status);
417 else
419 GitStatus stat;
420 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
421 if (stat.status)
423 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
424 // if ((stat.status->entry)&&(stat.status->entry->uuid))
425 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
428 else
430 // sometimes, git_client_status() returns with an error.
431 // in that case, we have to check if the working copy is versioned
432 // anyway to show the 'correct' context menu
433 if (askedpath.HasAdminDir())
434 status = git_wc_status_normal;
438 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
439 itemStatesFolder |= askedpath.GetAdminDirMask();
441 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
442 itemStates &= ~ITEMIS_INGIT;
444 if (status == git_wc_status_normal)
445 itemStatesFolder |= ITEMIS_NORMAL;
446 if (status == git_wc_status_conflicted)
447 itemStatesFolder |= ITEMIS_CONFLICTED;
448 if (status == git_wc_status_added)
449 itemStatesFolder |= ITEMIS_ADDED;
450 if (status == git_wc_status_deleted)
451 itemStatesFolder |= ITEMIS_DELETED;
454 catch ( ... )
456 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
459 else
461 status = fetchedstatus;
463 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
464 itemStatesFolder |= askedpath.GetAdminDirMask();
466 if (status == git_wc_status_ignored)
467 itemStatesFolder |= ITEMIS_IGNORED;
468 itemStatesFolder |= ITEMIS_FOLDER;
469 if (files_.empty())
470 itemStates |= ITEMIS_ONLYONE;
471 if (m_State != FileStateDropHandler)
472 itemStates |= itemStatesFolder;
474 else
476 folder_.clear();
477 status = fetchedstatus;
480 if (files_.size() == 2)
481 itemStates |= ITEMIS_TWO;
482 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
484 itemStates |= ITEMIS_ONLYONE;
485 if (m_State != FileStateDropHandler)
487 if (PathIsDirectory(files_.front().c_str()))
489 folder_ = files_.front();
490 git_wc_status_kind status = git_wc_status_none;
491 CTGitPath askedpath;
492 askedpath.SetFromWin(folder_.c_str());
494 if (folder_.compare(statuspath)!=0)
498 GitStatus stat;
499 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
500 if (stat.status)
502 status = GitStatus::GetMoreImportant(stat.status->text_status, stat.status->prop_status);
503 // if ((stat.status->entry)&&(stat.status->entry->uuid))
504 // uuidTarget = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
507 catch ( ... )
509 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
512 else
514 status = fetchedstatus;
516 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
517 itemStates |= askedpath.GetAdminDirMask();
519 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
520 itemStates &= ~ITEMIS_INGIT;
522 if (status == git_wc_status_ignored)
523 itemStates |= ITEMIS_IGNORED;
524 itemStates |= ITEMIS_FOLDER;
525 if (status == git_wc_status_added)
526 itemStates |= ITEMIS_ADDED;
527 if (status == git_wc_status_deleted)
528 itemStates |= ITEMIS_DELETED;
535 return S_OK;
538 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
540 TCHAR menutextbuffer[512] = {0};
541 TCHAR verbsbuffer[255] = {0};
542 MAKESTRING(stringid);
544 if (istop)
546 //menu entry for the top context menu, so append an "Git " before
547 //the menu text to indicate where the entry comes from
548 wcscpy_s(menutextbuffer, 255, L"Git ");
549 if (!g_ShellCache.HasShellMenuAccelerators())
551 // remove the accelerators
552 tstring temp = stringtablebuffer;
553 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
554 wcscpy_s(stringtablebuffer, 255, temp.c_str());
557 wcscat_s(menutextbuffer, 255, stringtablebuffer);
559 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
560 // so we have an easy and fast way to check the current branch
561 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
562 if (com == ShellMenuCommit)
564 // get branch name
565 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
566 CString sProjectRoot;
567 CString sBranchName;
569 if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
571 if (istop)
572 wcscpy_s(menutextbuffer, 255, L"Git ");
573 else
574 menutextbuffer[0] = L'\0';
575 MAKESTRING(IDS_MENUCOMMITSUBMODULE);
576 wcscat_s(menutextbuffer, 255, stringtablebuffer);
579 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
581 if (sBranchName.GetLength() == 40)
583 // if SHA1 only show 4 first bytes
584 BOOL bIsSha1 = TRUE;
585 for (int i=0; i<40; i++)
586 if ( !iswxdigit(sBranchName[i]) )
588 bIsSha1 = FALSE;
589 break;
591 if (bIsSha1)
592 sBranchName = sBranchName.Left(8) + L"...";
595 // sanity check
596 if (sBranchName.GetLength() > 64)
597 sBranchName = sBranchName.Left(64) + L"...";
599 // scan to before "..."
600 LPTSTR s = menutextbuffer + wcslen(menutextbuffer)-1;
601 if (s > menutextbuffer)
603 while (s > menutextbuffer)
605 if (*s != L'.')
607 s++;
608 break;
610 s--;
613 else
615 s = menutextbuffer;
618 // append branch name and end with ...
619 wcscpy_s(s, 255 - wcslen(menutextbuffer) - 1, L" -> \"" + sBranchName + L"\"...");
623 if (com == ShellMenuDiffLater)
625 std::wstring sPath = regDiffLater;
626 if (!sPath.empty())
628 // add the path of the saved file
629 wchar_t compact[40] = {0};
630 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
631 MAKESTRING(IDS_MENUDIFFNOW);
632 CString sMenu;
633 sMenu.Format(CString(stringtablebuffer), compact);
634 wcscpy_s(menutextbuffer, sMenu);
638 MENUITEMINFO menuiteminfo = { 0 };
639 menuiteminfo.cbSize = sizeof(menuiteminfo);
640 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
641 menuiteminfo.fType = MFT_STRING;
642 menuiteminfo.dwTypeData = menutextbuffer;
643 if (icon)
645 menuiteminfo.fMask |= MIIM_BITMAP;
646 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
648 menuiteminfo.wID = (UINT)id;
649 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
651 if (istop)
653 //menu entry for the top context menu, so append an "Git " before
654 //the menu text to indicate where the entry comes from
655 wcscpy_s(menutextbuffer, 255, L"Git ");
657 LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
658 wcscat_s(menutextbuffer, 255, verbsbuffer);
659 auto verb = std::wstring(menutextbuffer);
660 if (verb.find('&') != -1)
662 verb.erase(verb.find('&'),1);
664 myVerbsMap[verb] = id - idCmdFirst;
665 myVerbsMap[verb] = id;
666 myVerbsIDMap[id - idCmdFirst] = verb;
667 myVerbsIDMap[id] = verb;
668 // We store the relative and absolute diameter
669 // (drawitem callback uses absolute, others relative)
670 myIDMap[id - idCmdFirst] = com;
671 myIDMap[id] = com;
672 if (!istop)
673 mySubMenuMap[pos] = com;
676 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring& tempfile)
678 tempfile = std::wstring();
679 //write all selected files and paths to a temporary file
680 //for TortoiseGitProc.exe to read out again.
681 DWORD written = 0;
682 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
683 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
684 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
685 GetTortoiseGitTempPath(pathlength+1, path.get());
686 GetTempFileName(path.get(), L"git", 0, tempFile.get());
687 tempfile = std::wstring(tempFile.get());
689 CAutoFile file = ::CreateFile(tempFile.get(),
690 GENERIC_WRITE,
691 FILE_SHARE_READ,
692 nullptr,
693 CREATE_ALWAYS,
694 FILE_ATTRIBUTE_TEMPORARY,
695 nullptr);
697 if (!file)
698 return false;
700 if (!IsClipboardFormatAvailable(CF_HDROP))
701 return false;
702 if (!OpenClipboard(nullptr))
703 return false;
705 std::wstring sClipboardText;
706 HGLOBAL hglb = GetClipboardData(CF_HDROP);
707 SCOPE_EXIT
709 GlobalUnlock(hglb);
710 CloseClipboard();
712 HDROP hDrop = (HDROP)GlobalLock(hglb);
713 if (!hDrop)
714 return false;
715 SCOPE_EXIT { GlobalUnlock(hDrop); };
717 TCHAR szFileName[MAX_PATH] = {0};
718 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
719 for(UINT i = 0; i < cFiles; ++i)
721 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
722 std::wstring filename = szFileName;
723 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
724 ::WriteFile(file, L"\n", 2, &written, 0);
727 return true;
730 std::wstring CShellExt::WriteFileListToTempFile()
732 //write all selected files and paths to a temporary file
733 //for TortoiseGitProc.exe to read out again.
734 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
735 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
736 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
737 GetTortoiseGitTempPath(pathlength + 1, path.get());
738 GetTempFileName(path.get(), L"git", 0, tempFile.get());
739 auto retFilePath = std::wstring(tempFile.get());
741 CAutoFile file = ::CreateFile (tempFile.get(),
742 GENERIC_WRITE,
743 FILE_SHARE_READ,
744 nullptr,
745 CREATE_ALWAYS,
746 FILE_ATTRIBUTE_TEMPORARY,
747 nullptr);
749 if (!file)
751 MessageBox(nullptr, L"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile.get()), L"TortoiseGit", MB_ICONERROR);
752 return std::wstring();
755 DWORD written = 0;
756 if (files_.empty())
758 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
759 ::WriteFile(file, L"\n", 2, &written, 0);
762 for (const auto& file_ : files_)
764 ::WriteFile(file, file_.c_str(), (DWORD)file_.size() * sizeof(TCHAR), &written, 0);
765 ::WriteFile(file, L"\n", 2, &written, 0);
767 return retFilePath;
770 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
772 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
773 return S_OK;
775 PreserveChdir preserveChdir;
776 LoadLangDll();
778 if ((uFlags & CMF_DEFAULTONLY)!=0)
779 return S_OK; //we don't change the default action
781 if (files_.empty() || folder_.empty())
782 return S_OK;
784 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
785 return S_OK;
787 bool bSourceAndTargetFromSameRepository = ((uuidSource.size() == uuidTarget.size() && _wcsnicmp(uuidSource.c_str(), uuidTarget.c_str(), uuidSource.size()) == 0)) || uuidSource.empty() || uuidTarget.empty();
789 //the drop handler only has eight commands, but not all are visible at the same time:
790 //if the source file(s) are under version control then those files can be moved
791 //to the new location or they can be moved with a rename,
792 //if they are unversioned then they can be added to the working copy
793 //if they are versioned, they also can be exported to an unversioned location
794 UINT idCmd = idCmdFirst;
796 bool moveAvailable = false;
797 // Git move here
798 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
799 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && ((~itemStates) & ITEMIS_ADDED)))
801 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
802 moveAvailable = true;
805 // Git move and rename here
806 // available if source is a single, versioned but not added item, target is versioned, source and target from same repository or target folder is added
807 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE) && ((~itemStates) & ITEMIS_ADDED))
809 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
810 moveAvailable = true;
813 // Git copy here
814 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
815 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
816 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
818 // Git copy and rename here, source and target from same repository
819 // available if source is a single, versioned but not added item, target is versioned or target folder is added
820 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
821 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
823 // Git add here
824 // available if target is versioned and source is either unversioned or from another repository
825 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
826 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
828 // Git export here
829 // available if source is versioned and a folder
830 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
831 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
833 // Git export all here
834 // available if source is versioned and a folder
835 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
836 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
838 // apply patch
839 // available if source is a patchfile
840 if (itemStates & ITEMIS_PATCHFILE)
842 if (itemStates & ITEMIS_ONLYONE)
843 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
844 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUIMPORTPATCH, 0, idCmdFirst, ShellMenuImportPatchDrop, uFlags);
847 // separator
848 if (idCmd != idCmdFirst)
849 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
851 TweakMenu(hMenu);
853 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
856 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
857 UINT indexMenu,
858 UINT idCmdFirst,
859 UINT idCmdLast,
860 UINT uFlags)
862 __try
864 return QueryContextMenu_Wrap(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
866 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
869 return E_FAIL;
872 STDMETHODIMP CShellExt::QueryContextMenu_Wrap(HMENU hMenu,
873 UINT indexMenu,
874 UINT idCmdFirst,
875 UINT /*idCmdLast*/,
876 UINT uFlags)
878 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
879 PreserveChdir preserveChdir;
881 //first check if our drop handler is called
882 //and then (if true) provide the context menu for the
883 //drop handler
884 if (m_State == FileStateDropHandler)
886 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
889 if ((uFlags & CMF_DEFAULTONLY)!=0)
890 return S_OK; //we don't change the default action
892 if (files_.empty() && folder_.empty())
893 return S_OK;
895 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
896 return S_OK;
898 int csidlarray[] =
900 CSIDL_BITBUCKET,
901 CSIDL_CDBURN_AREA,
902 CSIDL_COMMON_FAVORITES,
903 CSIDL_COMMON_STARTMENU,
904 CSIDL_COMPUTERSNEARME,
905 CSIDL_CONNECTIONS,
906 CSIDL_CONTROLS,
907 CSIDL_COOKIES,
908 CSIDL_FAVORITES,
909 CSIDL_FONTS,
910 CSIDL_HISTORY,
911 CSIDL_INTERNET,
912 CSIDL_INTERNET_CACHE,
913 CSIDL_NETHOOD,
914 CSIDL_NETWORK,
915 CSIDL_PRINTERS,
916 CSIDL_PRINTHOOD,
917 CSIDL_RECENT,
918 CSIDL_SENDTO,
919 CSIDL_STARTMENU,
922 if (IsIllegalFolder(folder_, csidlarray))
923 return S_OK;
925 if (folder_.empty())
927 // folder is empty, but maybe files are selected
928 if (files_.empty())
929 return S_OK; // nothing selected - we don't have a menu to show
930 // check whether a selected entry is an UID - those are namespace extensions
931 // which we can't handle
932 for (const auto& file : files_)
934 if (CStringUtils::StartsWith(file.c_str(), L"::{"))
935 return S_OK;
938 else
940 if (CStringUtils::StartsWith(folder_.c_str(), L"::{"))
941 return S_OK;
944 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
946 if ((itemStates & (ITEMIS_INGIT | ITEMIS_INVERSIONEDFOLDER | ITEMIS_FOLDERINGIT | ITEMIS_BAREREPO)) == 0)
947 return S_OK;
950 //check if our menu is requested for a git admin directory
951 if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
952 return S_OK;
954 if (uFlags & CMF_EXTENDEDVERBS)
955 itemStates |= ITEMIS_EXTENDED;
957 regDiffLater.read();
958 if (!std::wstring(regDiffLater).empty())
959 itemStates |= ITEMIS_HASDIFFLATER;
961 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
962 if ( bShortcut && (files_.size()==1))
964 // Don't show the context menu for a link if the
965 // destination is not part of a working copy.
966 // It would only show the standard menu items
967 // which are already shown for the lnk-file.
968 CString path = files_.front().c_str();
969 if (!GitAdminDir::HasAdminDir(path))
971 return S_OK;
975 //check if we already added our menu entry for a folder.
976 //we check that by iterating through all menu entries and check if
977 //the dwItemData member points to our global ID string. That string is set
978 //by our shell extension when the folder menu is inserted.
979 TCHAR menubuf[MAX_PATH] = {0};
980 int count = GetMenuItemCount(hMenu);
981 for (int i=0; i<count; ++i)
983 MENUITEMINFO miif = { 0 };
984 miif.cbSize = sizeof(MENUITEMINFO);
985 miif.fMask = MIIM_DATA;
986 miif.dwTypeData = menubuf;
987 miif.cch = _countof(menubuf);
988 GetMenuItemInfo(hMenu, i, TRUE, &miif);
989 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
990 return S_OK;
993 LoadLangDll();
994 UINT idCmd = idCmdFirst;
996 //create the sub menu
997 HMENU subMenu = CreateMenu();
998 int indexSubMenu = 0;
1000 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
1001 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
1002 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
1004 int menuIndex = 0;
1005 bool bAddSeparator = false;
1006 bool bMenuEntryAdded = false;
1007 bool bMenuEmpty = true;
1008 // insert separator at start
1009 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1010 bool bShowIcons = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY));
1012 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1014 if (menuInfo[menuIndex].command == ShellSeparator)
1016 // we don't add a separator immediately. Because there might not be
1017 // another 'normal' menu entry after we insert a separator.
1018 // we simply set a flag here, indicating that before the next
1019 // 'normal' menu entry, a separator should be added.
1020 if (!bMenuEmpty)
1021 bAddSeparator = true;
1022 if (bMenuEntryAdded)
1023 bAddSeparator = true;
1025 else
1027 // check the conditions whether to show the menu entry or not
1028 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
1029 if (menuInfo[menuIndex].menuID & menuex)
1031 if( !(itemStates & ITEMIS_EXTENDED) )
1032 bInsertMenu = false;
1035 if (menuInfo[menuIndex].menuID & (~menumask))
1037 if (bInsertMenu)
1039 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1040 // insert a separator
1041 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1043 bAddSeparator = false;
1044 bMenuEntryAdded = false;
1045 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, nullptr);
1046 idCmd++;
1049 // handle special cases (sub menus)
1050 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1052 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1053 bMenuEntryAdded = true;
1055 else
1057 bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1059 // insert the menu entry
1060 InsertGitMenu( bIsTop,
1061 bIsTop ? hMenu : subMenu,
1062 bIsTop ? indexMenu++ : indexSubMenu++,
1063 idCmd++,
1064 menuInfo[menuIndex].menuTextID,
1065 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1066 idCmdFirst,
1067 menuInfo[menuIndex].command,
1068 uFlags);
1069 if (!bIsTop)
1071 bMenuEntryAdded = true;
1072 bMenuEmpty = false;
1073 bAddSeparator = false;
1079 menuIndex++;
1082 // do not show TortoiseGit menu if it's empty
1083 if (bMenuEmpty)
1085 if (idCmd - idCmdFirst > 0)
1087 //separator after
1088 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1090 TweakMenu(hMenu);
1092 //return number of menu items added
1093 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1096 //add sub menu to main context menu
1097 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1098 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1099 MAKESTRING(IDS_MENUSUBMENU);
1100 if (!g_ShellCache.HasShellMenuAccelerators())
1102 // remove the accelerators
1103 tstring temp = stringtablebuffer;
1104 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1105 wcscpy_s(stringtablebuffer, temp.c_str());
1107 MENUITEMINFO menuiteminfo = { 0 };
1108 menuiteminfo.cbSize = sizeof(menuiteminfo);
1109 menuiteminfo.fType = MFT_STRING;
1110 menuiteminfo.dwTypeData = stringtablebuffer;
1112 UINT uIcon = bShowIcons ? IDI_APP : 0;
1113 if (!folder_.empty())
1115 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1116 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1117 myIDMap[idCmd] = ShellSubMenuFolder;
1118 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1120 else if (!bShortcut && (files_.size()==1))
1122 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1123 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1124 myIDMap[idCmd] = ShellSubMenuFile;
1126 else if (bShortcut && (files_.size()==1))
1128 uIcon = bShowIcons ? IDI_MENULINK : 0;
1129 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1130 myIDMap[idCmd] = ShellSubMenuLink;
1132 else if (!files_.empty())
1134 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1135 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1136 myIDMap[idCmd] = ShellSubMenuMultiple;
1138 else
1140 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1141 myIDMap[idCmd] = ShellSubMenu;
1143 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1144 if (uIcon)
1146 menuiteminfo.fMask |= MIIM_BITMAP;
1147 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon);
1149 menuiteminfo.hSubMenu = subMenu;
1150 menuiteminfo.wID = idCmd++;
1151 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1153 //separator after
1154 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1156 TweakMenu(hMenu);
1158 //return number of menu items added
1159 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1162 void CShellExt::TweakMenu(HMENU hMenu)
1164 MENUINFO MenuInfo = {};
1165 MenuInfo.cbSize = sizeof(MenuInfo);
1166 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1167 MenuInfo.dwStyle = MNS_CHECKORBMP;
1168 SetMenuInfo(hMenu, &MenuInfo);
1171 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1173 gitCmd += command;
1174 gitCmd += L" /path:\"";
1175 if ((bFilesAllowed) && !files_.empty())
1176 gitCmd += files_.front();
1177 else
1178 gitCmd += folder_;
1179 gitCmd += L'"';
1182 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1184 tstring tempfile = WriteFileListToTempFile();
1185 gitCmd += command;
1186 gitCmd += L" /pathfile:\"";
1187 gitCmd += tempfile;
1188 gitCmd += L'"';
1189 gitCmd += L" /deletepathfile";
1192 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1194 tstring tempfile = WriteFileListToTempFile();
1195 gitCmd += command;
1196 gitCmd += L" /pathfile:\"";
1197 gitCmd += tempfile;
1198 gitCmd += L'"';
1199 gitCmd += L" /deletepathfile";
1200 gitCmd += L" /droptarget:\"";
1201 gitCmd += folder_;
1202 gitCmd += L'"';
1205 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1207 __try
1209 return InvokeCommand_Wrap(lpcmi);
1211 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1214 return E_FAIL;
1217 // This is called when you invoke a command on the menu:
1218 STDMETHODIMP CShellExt::InvokeCommand_Wrap(LPCMINVOKECOMMANDINFO lpcmi)
1220 PreserveChdir preserveChdir;
1221 HRESULT hr = E_INVALIDARG;
1222 if (!lpcmi)
1223 return hr;
1225 if (!files_.empty() || !folder_.empty())
1227 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1229 if (HIWORD(lpcmi->lpVerb))
1231 auto verb = std::wstring(MultibyteToWide(lpcmi->lpVerb));
1232 const auto verb_it = myVerbsMap.lower_bound(verb);
1233 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1234 idCmd = verb_it->second;
1235 else
1236 return hr;
1239 // See if we have a handler interface for this id
1240 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1241 if (id_it != myIDMap.end() && id_it->first == idCmd)
1243 tstring tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitProc.exe");
1244 tstring tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitMerge.exe");
1246 //TortoiseGitProc expects a command line of the form:
1247 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1248 // or
1249 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1251 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1252 //* pathfile is a path to a temporary file which contains a list of file paths
1253 std::wstring gitCmd = L" /command:";
1254 std::wstring tempfile;
1255 switch (id_it->second)
1257 //#region case
1258 case ShellMenuSync:
1260 TCHAR syncSeq[12] = { 0 };
1261 swprintf_s(syncSeq, L"%d", g_syncSeq++);
1262 AddPathCommand(gitCmd, L"sync", false);
1263 gitCmd += L" /seq:";
1264 gitCmd += syncSeq;
1266 break;
1267 case ShellMenuSubSync:
1268 AddPathFileCommand(gitCmd, L"subsync");
1269 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1271 gitCmd += L" /bkpath:\"";
1272 gitCmd += folder_;
1273 gitCmd += L'"';
1275 break;
1276 case ShellMenuUpdateExt:
1277 AddPathFileCommand(gitCmd, L"subupdate");
1278 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1280 gitCmd += L" /bkpath:\"";
1281 gitCmd += folder_;
1282 gitCmd += L'"';
1284 break;
1285 case ShellMenuCommit:
1286 AddPathFileCommand(gitCmd, L"commit");
1287 break;
1288 case ShellMenuAdd:
1289 AddPathFileCommand(gitCmd, L"add");
1290 break;
1291 case ShellMenuIgnore:
1292 AddPathFileCommand(gitCmd, L"ignore");
1293 break;
1294 case ShellMenuIgnoreCaseSensitive:
1295 AddPathFileCommand(gitCmd, L"ignore");
1296 gitCmd += L" /onlymask";
1297 break;
1298 case ShellMenuDeleteIgnore:
1299 AddPathFileCommand(gitCmd, L"ignore");
1300 gitCmd += L" /delete";
1301 break;
1302 case ShellMenuDeleteIgnoreCaseSensitive:
1303 AddPathFileCommand(gitCmd, L"ignore");
1304 gitCmd += L" /delete /onlymask";
1305 break;
1306 case ShellMenuUnIgnore:
1307 AddPathFileCommand(gitCmd, L"unignore");
1308 break;
1309 case ShellMenuUnIgnoreCaseSensitive:
1310 AddPathFileCommand(gitCmd, L"unignore");
1311 gitCmd += L" /onlymask";
1312 break;
1313 case ShellMenuMergeAbort:
1314 AddPathCommand(gitCmd, L"merge", false);
1315 gitCmd += L" /abort";
1316 break;
1317 case ShellMenuRevert:
1318 AddPathFileCommand(gitCmd, L"revert");
1319 break;
1320 case ShellMenuCleanup:
1321 AddPathFileCommand(gitCmd, L"cleanup");
1322 break;
1323 case ShellMenuSendMail:
1324 AddPathFileCommand(gitCmd, L"sendmail");
1325 break;
1326 case ShellMenuResolve:
1327 AddPathFileCommand(gitCmd, L"resolve");
1328 break;
1329 case ShellMenuSwitch:
1330 AddPathCommand(gitCmd, L"switch", false);
1331 break;
1332 case ShellMenuExport:
1333 AddPathCommand(gitCmd, L"export", false);
1334 break;
1335 case ShellMenuAbout:
1336 gitCmd += L"about";
1337 break;
1338 case ShellMenuCreateRepos:
1339 AddPathCommand(gitCmd, L"repocreate", false);
1340 break;
1341 case ShellMenuMerge:
1342 AddPathCommand(gitCmd, L"merge", false);
1343 break;
1344 case ShellMenuCopy:
1345 AddPathCommand(gitCmd, L"copy", true);
1346 break;
1347 case ShellMenuSettings:
1348 AddPathCommand(gitCmd, L"settings", true);
1349 break;
1350 case ShellMenuHelp:
1351 gitCmd += L"help";
1352 break;
1353 case ShellMenuRename:
1354 AddPathCommand(gitCmd, L"rename", true);
1355 break;
1356 case ShellMenuRemove:
1357 AddPathFileCommand(gitCmd, L"remove");
1358 if (itemStates & ITEMIS_SUBMODULE)
1359 gitCmd += L" /submodule";
1360 break;
1361 case ShellMenuRemoveKeep:
1362 AddPathFileCommand(gitCmd, L"remove");
1363 gitCmd += L" /keep";
1364 break;
1365 case ShellMenuDiff:
1366 gitCmd += L"diff /path:\"";
1367 if (files_.size() == 1)
1368 gitCmd += files_.front();
1369 else if (files_.size() == 2)
1371 auto I = files_.cbegin();
1372 gitCmd += *I;
1373 ++I;
1374 gitCmd += L"\" /path2:\"";
1375 gitCmd += *I;
1377 else
1378 gitCmd += folder_;
1379 gitCmd += L'"';
1380 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1381 gitCmd += L" /alternative";
1382 break;
1383 case ShellMenuDiffLater:
1384 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1386 gitCmd.clear();
1387 regDiffLater.removeValue();
1389 else if (files_.size() == 1)
1391 if (std::wstring(regDiffLater).empty())
1393 gitCmd.clear();
1394 regDiffLater = files_[0];
1396 else
1398 AddPathCommand(gitCmd, L"diff", true);
1399 gitCmd += L" /path2:\"";
1400 gitCmd += std::wstring(regDiffLater);
1401 gitCmd += L'"';
1402 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1403 gitCmd += L" /alternative";
1404 regDiffLater.removeValue();
1407 else
1408 gitCmd.clear();
1409 break;
1410 case ShellMenuPrevDiff:
1411 AddPathCommand(gitCmd, L"prevdiff", true);
1412 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1413 gitCmd += L" /alternative";
1414 break;
1415 case ShellMenuDiffTwo:
1416 AddPathCommand(gitCmd, L"diffcommits", true);
1417 break;
1418 case ShellMenuDropCopyAdd:
1419 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1420 break;
1421 case ShellMenuDropCopy:
1422 AddPathFileDropCommand(gitCmd, L"dropcopy");
1423 break;
1424 case ShellMenuDropCopyRename:
1425 AddPathFileDropCommand(gitCmd, L"dropcopy");
1426 gitCmd += L" /rename";
1427 break;
1428 case ShellMenuDropMove:
1429 AddPathFileDropCommand(gitCmd, L"dropmove");
1430 break;
1431 case ShellMenuDropMoveRename:
1432 AddPathFileDropCommand(gitCmd, L"dropmove");
1433 gitCmd += L" /rename";
1434 break;
1435 case ShellMenuDropExport:
1436 AddPathFileDropCommand(gitCmd, L"dropexport");
1437 break;
1438 case ShellMenuDropExportExtended:
1439 AddPathFileDropCommand(gitCmd, L"dropexport");
1440 gitCmd += L" /extended";
1441 break;
1442 case ShellMenuLog:
1443 case ShellMenuLogSubmoduleFolder:
1444 AddPathCommand(gitCmd, L"log", true);
1445 if (id_it->second == ShellMenuLogSubmoduleFolder)
1446 gitCmd += L" /submodule";
1447 break;
1448 case ShellMenuDaemon:
1449 AddPathCommand(gitCmd, L"daemon", true);
1450 break;
1451 case ShellMenuRevisionGraph:
1452 AddPathCommand(gitCmd, L"revisiongraph", true);
1453 break;
1454 case ShellMenuConflictEditor:
1455 AddPathCommand(gitCmd, L"conflicteditor", true);
1456 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1457 gitCmd += L" /alternative";
1458 break;
1459 case ShellMenuGitSVNRebase:
1460 AddPathCommand(gitCmd, L"svnrebase", false);
1461 break;
1462 case ShellMenuGitSVNDCommit:
1463 AddPathCommand(gitCmd, L"svndcommit", true);
1464 break;
1465 case ShellMenuGitSVNDFetch:
1466 AddPathCommand(gitCmd, L"svnfetch", false);
1467 break;
1468 case ShellMenuGitSVNIgnore:
1469 AddPathCommand(gitCmd, L"svnignore", false);
1470 break;
1471 case ShellMenuRebase:
1472 AddPathCommand(gitCmd, L"rebase", false);
1473 break;
1474 case ShellMenuShowChanged:
1475 if (files_.size() > 1)
1476 AddPathFileCommand(gitCmd, L"repostatus");
1477 else
1478 AddPathCommand(gitCmd, L"repostatus", true);
1479 break;
1480 case ShellMenuRepoBrowse:
1481 AddPathCommand(gitCmd, L"repobrowser", false);
1482 break;
1483 case ShellMenuRefBrowse:
1484 AddPathCommand(gitCmd, L"refbrowse", false);
1485 break;
1486 case ShellMenuRefLog:
1487 AddPathCommand(gitCmd, L"reflog", false);
1488 break;
1489 case ShellMenuStashSave:
1490 AddPathCommand(gitCmd, L"stashsave", true);
1491 break;
1492 case ShellMenuStashApply:
1493 AddPathCommand(gitCmd, L"stashapply", false);
1494 break;
1495 case ShellMenuStashPop:
1496 AddPathCommand(gitCmd, L"stashpop", false);
1497 break;
1498 case ShellMenuStashList:
1499 AddPathCommand(gitCmd, L"reflog", false);
1500 gitCmd += L" /ref:refs/stash";
1501 break;
1502 case ShellMenuBisectStart:
1503 AddPathCommand(gitCmd, L"bisect", false);
1504 gitCmd += L" /start";
1505 break;
1506 case ShellMenuBisectGood:
1507 AddPathCommand(gitCmd, L"bisect", false);
1508 gitCmd += L" /good";
1509 break;
1510 case ShellMenuBisectBad:
1511 AddPathCommand(gitCmd, L"bisect", false);
1512 gitCmd += L" /bad";
1513 break;
1514 case ShellMenuBisectSkip:
1515 AddPathCommand(gitCmd, L"bisect", false);
1516 gitCmd += L" /skip";
1517 break;
1518 case ShellMenuBisectReset:
1519 AddPathCommand(gitCmd, L"bisect", false);
1520 gitCmd += L" /reset";
1521 break;
1522 case ShellMenuSubAdd:
1523 AddPathCommand(gitCmd, L"subadd", false);
1524 break;
1525 case ShellMenuBlame:
1526 AddPathCommand(gitCmd, L"blame", true);
1527 break;
1528 case ShellMenuApplyPatch:
1529 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1531 // if there's a patch file in the clipboard, we save it
1532 // to a temporary file and tell TortoiseGitMerge to use that one
1533 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
1534 if (cFormat && OpenClipboard(nullptr))
1536 HGLOBAL hglb = GetClipboardData(cFormat);
1537 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1539 DWORD len = GetTortoiseGitTempPath(0, nullptr);
1540 auto path = std::make_unique<TCHAR[]>(len + 1);
1541 auto tempF = std::make_unique<TCHAR[]>(len + 100);
1542 GetTortoiseGitTempPath(len + 1, path.get());
1543 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1544 std::wstring sTempFile = std::wstring(tempF.get());
1546 FILE * outFile;
1547 size_t patchlen = strlen(lpstr);
1548 _wfopen_s(&outFile, sTempFile.c_str(), L"wb");
1549 if(outFile)
1551 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1552 if (size == patchlen)
1554 itemStates |= ITEMIS_PATCHFILE;
1555 files_.clear();
1556 files_.push_back(sTempFile);
1558 fclose(outFile);
1560 GlobalUnlock(hglb);
1561 CloseClipboard();
1564 if (itemStates & ITEMIS_PATCHFILE)
1566 gitCmd = L" /diff:\"";
1567 if (!files_.empty())
1569 gitCmd += files_.front();
1570 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1572 gitCmd += L"\" /patchpath:\"";
1573 gitCmd += folder_;
1576 else
1577 gitCmd += folder_;
1578 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1579 gitCmd += L"\" /wc";
1580 else
1581 gitCmd += L'"';
1583 else
1585 gitCmd = L" /patchpath:\"";
1586 if (!files_.empty())
1587 gitCmd += files_.front();
1588 else
1589 gitCmd += folder_;
1590 gitCmd += L'"';
1592 myIDMap.clear();
1593 myVerbsIDMap.clear();
1594 myVerbsMap.clear();
1595 RunCommand(tortoiseMergePath, gitCmd, L"TortoiseGitMerge launch failed");
1596 return S_OK;
1597 break;
1598 case ShellMenuClipPaste:
1599 if (WriteClipboardPathsToTempFile(tempfile))
1601 bool bCopy = true;
1602 UINT cPrefDropFormat = RegisterClipboardFormat(L"Preferred DropEffect");
1603 if (cPrefDropFormat)
1605 if (OpenClipboard(lpcmi->hwnd))
1607 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1608 if (hglb)
1610 DWORD* effect = (DWORD*) GlobalLock(hglb);
1611 if (*effect == DROPEFFECT_MOVE)
1612 bCopy = false;
1613 GlobalUnlock(hglb);
1615 CloseClipboard();
1619 if (bCopy)
1620 gitCmd += L"pastecopy /pathfile:\"";
1621 else
1622 gitCmd += L"pastemove /pathfile:\"";
1623 gitCmd += tempfile;
1624 gitCmd += L'"';
1625 gitCmd += L" /deletepathfile";
1626 gitCmd += L" /droptarget:\"";
1627 gitCmd += folder_;
1628 gitCmd += L'"';
1630 else return S_OK;
1631 break;
1632 case ShellMenuClone:
1633 AddPathCommand(gitCmd, L"clone", false);
1634 break;
1635 case ShellMenuPull:
1636 AddPathCommand(gitCmd, L"pull", false);
1637 break;
1638 case ShellMenuPush:
1639 AddPathCommand(gitCmd, L"push", false);
1640 break;
1641 case ShellMenuBranch:
1642 AddPathCommand(gitCmd, L"branch", false);
1643 break;
1644 case ShellMenuTag:
1645 AddPathCommand(gitCmd, L"tag", false);
1646 break;
1647 case ShellMenuFormatPatch:
1648 AddPathCommand(gitCmd, L"formatpatch", false);
1649 break;
1650 case ShellMenuImportPatch:
1651 AddPathFileCommand(gitCmd, L"importpatch");
1652 break;
1653 case ShellMenuImportPatchDrop:
1654 AddPathFileDropCommand(gitCmd, L"importpatch");
1655 break;
1656 case ShellMenuFetch:
1657 AddPathCommand(gitCmd, L"fetch", false);
1658 break;
1660 default:
1661 break;
1662 //#endregion
1663 } // switch (id_it->second)
1664 if (!gitCmd.empty())
1666 gitCmd += L" /hwnd:";
1667 TCHAR buf[30] = { 0 };
1668 swprintf_s(buf, L"%p", (void*)lpcmi->hwnd);
1669 gitCmd += buf;
1670 myIDMap.clear();
1671 myVerbsIDMap.clear();
1672 myVerbsMap.clear();
1673 RunCommand(tortoiseProcPath, gitCmd, L"TortoiseProc launch failed");
1675 hr = S_OK;
1676 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1677 } // if (files_.empty() || folder_.empty())
1678 return hr;
1681 // This is for the status bar and things like that:
1682 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1683 UINT uFlags,
1684 UINT FAR * reserved,
1685 LPSTR pszName,
1686 UINT cchMax)
1688 __try
1690 return GetCommandString_Wrap(idCmd, uFlags, reserved, pszName, cchMax);
1692 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1695 return E_FAIL;
1698 // This is for the status bar and things like that:
1699 STDMETHODIMP CShellExt::GetCommandString_Wrap(UINT_PTR idCmd,
1700 UINT uFlags,
1701 UINT FAR * /*reserved*/,
1702 LPSTR pszName,
1703 UINT cchMax)
1705 PreserveChdir preserveChdir;
1706 //do we know the id?
1707 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1708 if (id_it == myIDMap.end() || id_it->first != idCmd)
1710 return E_INVALIDARG; //no, we don't
1713 LoadLangDll();
1714 HRESULT hr = E_INVALIDARG;
1716 MAKESTRING(IDS_MENUDESCDEFAULT);
1717 int menuIndex = 0;
1718 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1720 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1722 MAKESTRING(menuInfo[menuIndex].menuDescID);
1723 break;
1725 menuIndex++;
1728 const TCHAR * desc = stringtablebuffer;
1729 switch(uFlags)
1731 case GCS_HELPTEXTA:
1733 std::string help = WideToMultibyte(desc);
1734 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1735 hr = S_OK;
1736 break;
1738 case GCS_HELPTEXTW:
1740 std::wstring help = desc;
1741 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1742 hr = S_OK;
1743 break;
1745 case GCS_VERBA:
1747 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1748 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1750 std::string help = WideToMultibyte(verb_id_it->second);
1751 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1752 hr = S_OK;
1755 break;
1756 case GCS_VERBW:
1758 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1759 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1761 std::wstring help = verb_id_it->second;
1762 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1763 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1764 hr = S_OK;
1767 break;
1769 return hr;
1772 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1774 __try
1776 return HandleMenuMsg_Wrap(uMsg, wParam, lParam);
1778 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1781 return E_FAIL;
1784 STDMETHODIMP CShellExt::HandleMenuMsg_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam)
1786 LRESULT res;
1787 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1790 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1792 __try
1794 return HandleMenuMsg2_Wrap(uMsg, wParam, lParam, pResult);
1796 __except(CCrashReport::Instance().SendReport(GetExceptionInformation()))
1799 return E_FAIL;
1802 STDMETHODIMP CShellExt::HandleMenuMsg2_Wrap(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1804 PreserveChdir preserveChdir;
1806 LRESULT res;
1807 if (!pResult)
1808 pResult = &res;
1809 *pResult = FALSE;
1811 LoadLangDll();
1812 switch (uMsg)
1814 case WM_MEASUREITEM:
1816 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1817 if (!lpmis)
1818 break;
1819 lpmis->itemWidth = 16;
1820 lpmis->itemHeight = 16;
1821 *pResult = TRUE;
1823 break;
1824 case WM_DRAWITEM:
1826 LPCTSTR resource;
1827 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1828 if (!lpdis || lpdis->CtlType != ODT_MENU)
1829 return S_OK; //not for a menu
1830 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1831 if (!resource)
1832 return S_OK;
1833 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1834 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1835 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR);
1836 if (!hIcon)
1837 return S_OK;
1838 DrawIconEx(lpdis->hDC,
1839 lpdis->rcItem.left,
1840 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - iconHeight) / 2,
1841 hIcon, iconWidth, iconHeight,
1842 0, nullptr, DI_NORMAL);
1843 DestroyIcon(hIcon);
1844 *pResult = TRUE;
1846 break;
1847 case WM_MENUCHAR:
1849 TCHAR *szItem;
1850 if (HIWORD(wParam) != MF_POPUP)
1851 return S_OK;
1852 int nChar = LOWORD(wParam);
1853 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1854 nChar = tolower(nChar);
1855 // we have the char the user pressed, now search that char in all our
1856 // menu items
1857 std::vector<UINT_PTR> accmenus;
1858 for (auto It = mySubMenuMap.cbegin(); It != mySubMenuMap.cend(); ++It)
1860 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1861 if (!resource)
1862 continue;
1863 szItem = stringtablebuffer;
1864 TCHAR* amp = wcschr(szItem, L'&');
1865 if (!amp)
1866 continue;
1867 amp++;
1868 int ampChar = LOWORD(*amp);
1869 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1870 ampChar = tolower(ampChar);
1871 if (ampChar == nChar)
1873 // yep, we found a menu which has the pressed key
1874 // as an accelerator. Add that menu to the list to
1875 // process later.
1876 accmenus.push_back(It->first);
1879 if (accmenus.empty())
1881 // no menu with that accelerator key.
1882 *pResult = MAKELONG(0, MNC_IGNORE);
1883 return S_OK;
1885 if (accmenus.size() == 1)
1887 // Only one menu with that accelerator key. We're lucky!
1888 // So just execute that menu entry.
1889 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1890 return S_OK;
1892 if (accmenus.size() > 1)
1894 // we have more than one menu item with this accelerator key!
1895 MENUITEMINFO mif;
1896 mif.cbSize = sizeof(MENUITEMINFO);
1897 mif.fMask = MIIM_STATE;
1898 for (auto it = accmenus.cbegin(); it != accmenus.cend(); ++it)
1900 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1901 if (mif.fState == MFS_HILITE)
1903 // this is the selected item, so select the next one
1904 ++it;
1905 if (it == accmenus.end())
1906 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1907 else
1908 *pResult = MAKELONG(*it, MNC_SELECT);
1909 return S_OK;
1912 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1915 break;
1916 default:
1917 return S_OK;
1920 return S_OK;
1923 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1925 TCHAR textbuf[255] = { 0 };
1926 LPCTSTR resource = nullptr;
1927 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1928 space = 6;
1930 int menuIndex = 0;
1931 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1933 if (menuInfo[menuIndex].command == id)
1935 MAKESTRING(menuInfo[menuIndex].menuTextID);
1936 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1937 switch (id)
1939 case ShellSubMenuMultiple:
1940 case ShellSubMenuLink:
1941 case ShellSubMenuFolder:
1942 case ShellSubMenuFile:
1943 case ShellSubMenu:
1944 space = 0;
1945 break;
1946 default:
1947 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1948 if (layout & menuInfo[menuIndex].menuID)
1950 wcscpy_s(textbuf, 255, L"Git ");
1951 wcscat_s(textbuf, 255, stringtablebuffer);
1952 wcscpy_s(stringtablebuffer, 255, textbuf);
1954 break;
1956 return resource;
1958 menuIndex++;
1960 return nullptr;
1963 bool CShellExt::IsIllegalFolder(const std::wstring& folder, int* cslidarray)
1965 int i=0;
1966 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1967 LPITEMIDLIST pidl = nullptr;
1968 while (cslidarray[i])
1970 ++i;
1971 pidl = nullptr;
1972 if (SHGetFolderLocation(nullptr, cslidarray[i - 1], nullptr, 0, &pidl) != S_OK)
1973 continue;
1974 if (!SHGetPathFromIDList(pidl, buf))
1976 // not a file system path, definitely illegal for our use
1977 CoTaskMemFree(pidl);
1978 continue;
1980 CoTaskMemFree(pidl);
1981 if (!buf[0])
1982 continue;
1983 if (wcscmp(buf, folder.c_str()) == 0)
1984 return true;
1986 return false;
1989 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1991 HMENU ignoresubmenu = nullptr;
1992 int indexignoresub = 0;
1993 bool bShowIgnoreMenu = false;
1994 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1995 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1996 if (files_.empty())
1997 return false;
1998 UINT icon = bShowIcons ? IDI_IGNORE : 0;
2000 auto I = files_.cbegin();
2001 if (wcsrchr(I->c_str(), L'\\'))
2002 wcscpy_s(ignorepath, wcsrchr(I->c_str(), L'\\') + 1);
2003 else
2004 wcscpy_s(ignorepath, I->c_str());
2005 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
2007 // check if the item name is ignored or the mask
2008 size_t p = 0;
2009 const size_t pathLength = wcslen(ignorepath);
2010 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
2012 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
2013 && (p + pathLength == ignoredprops.length() || ignoredprops[p + pathLength + 1] == TCHAR('\n')))
2015 break;
2017 p++;
2019 if (p!=-1)
2021 ignoresubmenu = CreateMenu();
2022 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2023 auto verb = std::wstring(ignorepath);
2024 myVerbsMap[verb] = idCmd - idCmdFirst;
2025 myVerbsMap[verb] = idCmd;
2026 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2027 myVerbsIDMap[idCmd] = verb;
2028 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
2029 myIDMap[idCmd++] = ShellMenuUnIgnore;
2030 bShowIgnoreMenu = true;
2032 wcscpy_s(maskbuf, L"*");
2033 if (wcsrchr(ignorepath, L'.'))
2035 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
2036 p = ignoredprops.find(maskbuf);
2037 if ((p!=-1) &&
2038 ((ignoredprops.compare(maskbuf) == 0) || (ignoredprops.find(L'\n', p) == p + wcslen(maskbuf) + 1) || (ignoredprops.rfind(L'\n', p) == p - 1)))
2040 if (!ignoresubmenu)
2041 ignoresubmenu = CreateMenu();
2043 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2044 auto verb = std::wstring(maskbuf);
2045 myVerbsMap[verb] = idCmd - idCmdFirst;
2046 myVerbsMap[verb] = idCmd;
2047 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2048 myVerbsIDMap[idCmd] = verb;
2049 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
2050 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
2051 bShowIgnoreMenu = true;
2055 else if ((itemStates & ITEMIS_IGNORED) == 0)
2057 bShowIgnoreMenu = true;
2058 ignoresubmenu = CreateMenu();
2059 if (itemStates & ITEMIS_ONLYONE)
2061 if (itemStates & ITEMIS_INGIT)
2063 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2064 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2065 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2067 wcscpy_s(maskbuf, L"*");
2068 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
2070 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
2071 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2072 auto verb = std::wstring(maskbuf);
2073 myVerbsMap[verb] = idCmd - idCmdFirst;
2074 myVerbsMap[verb] = idCmd;
2075 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2076 myVerbsIDMap[idCmd] = verb;
2077 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2078 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2081 else
2083 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2084 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2085 myIDMap[idCmd++] = ShellMenuIgnore;
2087 wcscpy_s(maskbuf, L"*");
2088 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
2090 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
2091 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2092 auto verb = std::wstring(maskbuf);
2093 myVerbsMap[verb] = idCmd - idCmdFirst;
2094 myVerbsMap[verb] = idCmd;
2095 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2096 myVerbsIDMap[idCmd] = verb;
2097 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2098 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2102 else
2104 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2105 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2106 // has selected more than 16 files, we won't know about that here.
2107 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2108 if (itemStates & ITEMIS_INGIT)
2110 if (files_.size() >= 16)
2112 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2);
2113 wcscpy_s(ignorepath, stringtablebuffer);
2115 else
2117 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2118 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2120 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2121 auto verb = std::wstring(ignorepath);
2122 myVerbsMap[verb] = idCmd - idCmdFirst;
2123 myVerbsMap[verb] = idCmd;
2124 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2125 myVerbsIDMap[idCmd] = verb;
2126 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2127 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2129 if (files_.size() >= 16)
2131 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2);
2132 wcscpy_s(ignorepath, stringtablebuffer);
2134 else
2136 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2137 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2139 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2140 verb = std::wstring(ignorepath);
2141 myVerbsMap[verb] = idCmd - idCmdFirst;
2142 myVerbsMap[verb] = idCmd;
2143 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2144 myVerbsIDMap[idCmd] = verb;
2145 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2146 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2148 else
2150 if (files_.size() >= 16)
2152 MAKESTRING(IDS_MENUIGNOREMULTIPLE2);
2153 wcscpy_s(ignorepath, stringtablebuffer);
2155 else
2157 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2158 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2160 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2161 auto verb = std::wstring(ignorepath);
2162 myVerbsMap[verb] = idCmd - idCmdFirst;
2163 myVerbsMap[verb] = idCmd;
2164 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2165 myVerbsIDMap[idCmd] = verb;
2166 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2167 myIDMap[idCmd++] = ShellMenuIgnore;
2169 if (files_.size() >= 16)
2171 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2);
2172 wcscpy_s(ignorepath, stringtablebuffer);
2174 else
2176 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2177 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2179 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2180 verb = std::wstring(ignorepath);
2181 myVerbsMap[verb] = idCmd - idCmdFirst;
2182 myVerbsMap[verb] = idCmd;
2183 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2184 myVerbsIDMap[idCmd] = verb;
2185 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2186 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2191 if (bShowIgnoreMenu)
2193 MENUITEMINFO menuiteminfo = { 0 };
2194 menuiteminfo.cbSize = sizeof(menuiteminfo);
2195 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2196 if (icon)
2198 menuiteminfo.fMask |= MIIM_BITMAP;
2199 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
2201 menuiteminfo.fType = MFT_STRING;
2202 menuiteminfo.hSubMenu = ignoresubmenu;
2203 menuiteminfo.wID = idCmd;
2204 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2205 if (itemStates & ITEMIS_IGNORED)
2206 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2207 else if (itemStates & ITEMIS_INGIT)
2208 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2209 else
2210 GetMenuTextFromResource(ShellMenuIgnoreSub);
2211 menuiteminfo.dwTypeData = stringtablebuffer;
2212 menuiteminfo.cch = (UINT)min(wcslen(menuiteminfo.dwTypeData), UINT_MAX);
2214 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2215 if (itemStates & ITEMIS_IGNORED)
2217 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2218 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2220 else
2222 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2223 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2226 return bShowIgnoreMenu;
2229 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2231 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), command.c_str()))
2233 // process started - exit
2234 return;
2237 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONERROR);
2240 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2242 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2243 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2246 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2248 if (pair.yes && pair.no)
2250 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2251 return true;
2253 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2254 return true;
2255 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2256 return true;
2257 return false;