Don't set an exception filter for the shell extension
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blobc8dfbece8737ef48ef3e2485f1d875a1bdbec3c6
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016 - TortoiseSVN
4 // Copyright (C) 2008-2017 - 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 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: Initialize\n");
47 PreserveChdir preserveChdir;
48 files_.clear();
49 folder_.clear();
50 uuidSource.clear();
51 uuidTarget.clear();
52 itemStates = 0;
53 itemStatesFolder = 0;
54 std::wstring statuspath;
55 git_wc_status_kind fetchedstatus = git_wc_status_none;
56 // get selected files/folders
57 if (pDataObj)
59 STGMEDIUM medium;
60 FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
61 nullptr,
62 DVASPECT_CONTENT,
63 -1,
64 TYMED_HGLOBAL};
65 HRESULT hres = pDataObj->GetData(&fmte, &medium);
67 if (SUCCEEDED(hres) && medium.hGlobal)
69 if (m_State == FileStateDropHandler)
71 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
73 ReleaseStgMedium(&medium);
74 return S_OK;
77 FORMATETC etc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
78 STGMEDIUM stg = { TYMED_HGLOBAL };
79 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
81 ReleaseStgMedium ( &medium );
82 return E_INVALIDARG;
86 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
87 if (!drop)
89 ReleaseStgMedium ( &stg );
90 ReleaseStgMedium ( &medium );
91 return E_INVALIDARG;
94 int count = DragQueryFile(drop, (UINT)-1, nullptr, 0);
95 if (count == 1)
96 itemStates |= ITEMIS_ONLYONE;
97 for (int i = 0; i < count; i++)
99 // find the path length in chars
100 UINT len = DragQueryFile(drop, i, nullptr, 0);
101 if (len == 0)
102 continue;
103 auto szFileName = std::make_unique<TCHAR[]>(len + 1);
104 if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
105 continue;
106 auto str = std::wstring(szFileName.get());
107 if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
110 CTGitPath strpath;
111 strpath.SetFromWin(str.c_str());
112 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
113 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
115 files_.push_back(str);
116 if (i == 0)
118 //get the git status of the item
119 git_wc_status_kind status = git_wc_status_none;
120 CTGitPath askedpath;
121 askedpath.SetFromWin(str.c_str());
122 CString workTreePath;
123 askedpath.HasAdminDir(&workTreePath);
124 uuidSource = workTreePath;
127 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
129 CTGitPath tpath(str.c_str());
130 if (!tpath.HasAdminDir())
132 status = git_wc_status_none;
133 continue;
135 if (tpath.IsAdminDir())
137 status = git_wc_status_none;
138 continue;
140 TGITCacheResponse itemStatus;
141 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
142 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
144 fetchedstatus = status = (git_wc_status_kind)itemStatus.m_status;
145 if (askedpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
147 itemStates |= ITEMIS_FOLDER;
148 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
149 itemStates |= ITEMIS_FOLDERINGIT;
153 else
155 GitStatus stat;
156 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
157 if (stat.status)
159 statuspath = str;
160 status = stat.status->status;
161 fetchedstatus = status;
162 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
164 itemStates |= ITEMIS_FOLDER;
165 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
166 itemStates |= ITEMIS_FOLDERINGIT;
168 //if ((stat.status->entry)&&(stat.status->entry->uuid))
169 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
171 else
173 // sometimes, git_client_status() returns with an error.
174 // in that case, we have to check if the working copy is versioned
175 // anyway to show the 'correct' context menu
176 if (askedpath.HasAdminDir())
177 status = git_wc_status_normal;
181 catch ( ... )
183 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
186 // TODO: should we really assume any sub-directory to be versioned
187 // or only if it contains versioned files
188 itemStates |= askedpath.GetAdminDirMask();
190 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
191 itemStates &= ~ITEMIS_INGIT;
193 if (status == git_wc_status_ignored)
194 itemStates |= ITEMIS_IGNORED;
195 if (status == git_wc_status_normal)
196 itemStates |= ITEMIS_NORMAL;
197 if (status == git_wc_status_conflicted)
198 itemStates |= ITEMIS_CONFLICTED;
199 if (status == git_wc_status_added)
200 itemStates |= ITEMIS_ADDED;
201 if (status == git_wc_status_deleted)
202 itemStates |= ITEMIS_DELETED;
205 } // for (int i = 0; i < count; i++)
206 GlobalUnlock ( drop );
207 ReleaseStgMedium ( &stg );
209 } // if (m_State == FileStateDropHandler)
210 else
212 //Enumerate PIDLs which the user has selected
213 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
214 ItemIDList parent( GetPIDLFolder (cida));
216 int count = cida->cidl;
217 BOOL statfetched = FALSE;
218 for (int i = 0; i < count; ++i)
220 ItemIDList child (GetPIDLItem (cida, i), &parent);
221 std::wstring str = child.toString();
222 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
224 //check if our menu is requested for a git admin directory
225 if (GitAdminDir::IsAdminDirPath(str.c_str()))
226 continue;
228 files_.push_back(str);
229 CTGitPath strpath;
230 strpath.SetFromWin(str.c_str());
231 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
232 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
233 if (!statfetched)
235 //get the git status of the item
236 git_wc_status_kind status = git_wc_status_none;
237 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
239 if (strpath.HasAdminDir())
240 status = git_wc_status_normal;
242 else
246 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
248 CTGitPath tpath(str.c_str());
249 if(!tpath.HasAdminDir())
251 status = git_wc_status_none;
252 continue;
254 if(tpath.IsAdminDir())
256 status = git_wc_status_none;
257 continue;
259 TGITCacheResponse itemStatus;
260 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
261 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
263 fetchedstatus = status = (git_wc_status_kind)itemStatus.m_status;
264 if (strpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
266 itemStates |= ITEMIS_FOLDER;
267 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
268 itemStates |= ITEMIS_FOLDERINGIT;
270 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
271 itemStates |= ITEMIS_CONFLICTED;
274 else
276 GitStatus stat;
277 if (strpath.HasAdminDir())
278 stat.GetStatus(strpath, false, false, true);
279 statuspath = str;
280 if (stat.status)
282 status = stat.status->status;
283 fetchedstatus = status;
284 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
286 itemStates |= ITEMIS_FOLDER;
287 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
288 itemStates |= ITEMIS_FOLDERINGIT;
290 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
291 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
292 itemStates |= ITEMIS_CONFLICTED;
293 //if ((stat.status->entry)&&(stat.status->entry->uuid))
294 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
296 else
298 // sometimes, git_client_status() returns with an error.
299 // in that case, we have to check if the working copy is versioned
300 // anyway to show the 'correct' context menu
301 if (strpath.HasAdminDir())
303 status = git_wc_status_normal;
304 fetchedstatus = status;
308 statfetched = TRUE;
310 catch ( ... )
312 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
316 itemStates |= strpath.GetAdminDirMask();
318 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
319 itemStates &= ~ITEMIS_INGIT;
320 if (status == git_wc_status_ignored)
322 itemStates |= ITEMIS_IGNORED;
325 if (status == git_wc_status_normal)
326 itemStates |= ITEMIS_NORMAL;
327 if (status == git_wc_status_conflicted)
328 itemStates |= ITEMIS_CONFLICTED;
329 if (status == git_wc_status_added)
330 itemStates |= ITEMIS_ADDED;
331 if (status == git_wc_status_deleted)
332 itemStates |= ITEMIS_DELETED;
335 } // for (int i = 0; i < count; ++i)
336 ItemIDList child (GetPIDLItem (cida, 0), &parent);
337 if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
338 itemStates |= ITEMIS_INVERSIONEDFOLDER;
340 if (GitAdminDir::IsBareRepo(child.toString().c_str()))
341 itemStates = ITEMIS_BAREREPO;
343 GlobalUnlock(medium.hGlobal);
345 // if the item is a versioned folder, check if there's a patch file
346 // in the clipboard to be used in "Apply Patch"
347 UINT cFormatDiff = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
348 if (cFormatDiff)
350 if (IsClipboardFormatAvailable(cFormatDiff))
351 itemStates |= ITEMIS_PATCHINCLIPBOARD;
353 if (IsClipboardFormatAvailable(CF_HDROP))
354 itemStates |= ITEMIS_PATHINCLIPBOARD;
358 ReleaseStgMedium ( &medium );
359 if (medium.pUnkForRelease)
361 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
362 relInterface->Release();
367 // get folder background
368 if (pIDFolder)
370 ItemIDList list(pIDFolder);
371 folder_ = list.toString();
372 git_wc_status_kind status = git_wc_status_none;
373 if (IsClipboardFormatAvailable(CF_HDROP))
374 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
376 CTGitPath askedpath;
377 askedpath.SetFromWin(folder_.c_str());
379 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
381 if (folder_.compare(statuspath)!=0)
383 CString worktreePath;
384 askedpath.HasAdminDir(&worktreePath);
385 uuidTarget = worktreePath;
388 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
390 CTGitPath tpath(folder_.c_str());
391 if(!tpath.HasAdminDir())
392 status = git_wc_status_none;
393 else if(tpath.IsAdminDir())
394 status = git_wc_status_none;
395 else
397 TGITCacheResponse itemStatus;
398 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
399 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
400 status = (git_wc_status_kind)itemStatus.m_status;
403 else
405 GitStatus stat;
406 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
407 if (stat.status)
408 status = stat.status->status;
409 else
411 // sometimes, git_client_status() returns with an error.
412 // in that case, we have to check if the working copy is versioned
413 // anyway to show the 'correct' context menu
414 if (askedpath.HasAdminDir())
415 status = git_wc_status_normal;
419 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
420 itemStatesFolder |= askedpath.GetAdminDirMask();
422 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
423 itemStates &= ~ITEMIS_INGIT;
425 if (status == git_wc_status_normal)
426 itemStatesFolder |= ITEMIS_NORMAL;
427 if (status == git_wc_status_conflicted)
428 itemStatesFolder |= ITEMIS_CONFLICTED;
429 if (status == git_wc_status_added)
430 itemStatesFolder |= ITEMIS_ADDED;
431 if (status == git_wc_status_deleted)
432 itemStatesFolder |= ITEMIS_DELETED;
435 catch ( ... )
437 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
440 else
442 status = fetchedstatus;
444 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
445 itemStatesFolder |= askedpath.GetAdminDirMask();
447 if (status == git_wc_status_ignored)
448 itemStatesFolder |= ITEMIS_IGNORED;
449 itemStatesFolder |= ITEMIS_FOLDER;
450 if (files_.empty())
451 itemStates |= ITEMIS_ONLYONE;
452 if (m_State != FileStateDropHandler)
453 itemStates |= itemStatesFolder;
455 else
457 folder_.clear();
458 status = fetchedstatus;
461 if (files_.size() == 2)
462 itemStates |= ITEMIS_TWO;
463 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
465 itemStates |= ITEMIS_ONLYONE;
466 if (m_State != FileStateDropHandler)
468 if (PathIsDirectory(files_.front().c_str()))
470 folder_ = files_.front();
471 git_wc_status_kind status = git_wc_status_none;
472 CTGitPath askedpath;
473 askedpath.SetFromWin(folder_.c_str());
475 if (folder_.compare(statuspath)!=0)
479 GitStatus stat;
480 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
481 if (stat.status)
482 status = stat.status->status;
484 catch ( ... )
486 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
489 else
491 status = fetchedstatus;
493 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
494 itemStates |= askedpath.GetAdminDirMask();
496 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
497 itemStates &= ~ITEMIS_INGIT;
499 if (status == git_wc_status_ignored)
500 itemStates |= ITEMIS_IGNORED;
501 itemStates |= ITEMIS_FOLDER;
502 if (status == git_wc_status_added)
503 itemStates |= ITEMIS_ADDED;
504 if (status == git_wc_status_deleted)
505 itemStates |= ITEMIS_DELETED;
512 return S_OK;
515 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
517 TCHAR menutextbuffer[512] = {0};
518 TCHAR verbsbuffer[255] = {0};
519 MAKESTRING(stringid);
521 if (istop)
523 //menu entry for the top context menu, so append an "Git " before
524 //the menu text to indicate where the entry comes from
525 wcscpy_s(menutextbuffer, 255, L"Git ");
526 if (!g_ShellCache.HasShellMenuAccelerators())
528 // remove the accelerators
529 tstring temp = stringtablebuffer;
530 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
531 wcscpy_s(stringtablebuffer, 255, temp.c_str());
534 wcscat_s(menutextbuffer, 255, stringtablebuffer);
536 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
537 // so we have an easy and fast way to check the current branch
538 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
539 if (com == ShellMenuCommit)
541 // get branch name
542 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
543 CString sProjectRoot;
544 CString sBranchName;
546 if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
548 if (istop)
549 wcscpy_s(menutextbuffer, 255, L"Git ");
550 else
551 menutextbuffer[0] = L'\0';
552 MAKESTRING(IDS_MENUCOMMITSUBMODULE);
553 wcscat_s(menutextbuffer, 255, stringtablebuffer);
556 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
558 if (sBranchName.GetLength() == 2 * GIT_HASH_SIZE)
560 // if SHA1 only show 4 first bytes
561 BOOL bIsSha1 = TRUE;
562 for (int i = 0; i < 2 * GIT_HASH_SIZE; ++i)
563 if ( !iswxdigit(sBranchName[i]) )
565 bIsSha1 = FALSE;
566 break;
568 if (bIsSha1)
569 sBranchName = sBranchName.Left(8) + L"...";
572 // sanity check
573 if (sBranchName.GetLength() > 64)
574 sBranchName = sBranchName.Left(64) + L"...";
576 // scan to before "..."
577 LPTSTR s = menutextbuffer + wcslen(menutextbuffer)-1;
578 if (s > menutextbuffer)
580 while (s > menutextbuffer)
582 if (*s != L'.')
584 s++;
585 break;
587 s--;
590 else
592 s = menutextbuffer;
595 // append branch name and end with ...
596 wcscpy_s(s, 255 - wcslen(menutextbuffer) - 1, L" -> \"" + sBranchName + L"\"...");
600 if (com == ShellMenuDiffLater)
602 std::wstring sPath = regDiffLater;
603 if (!sPath.empty())
605 // add the path of the saved file
606 wchar_t compact[2 * GIT_HASH_SIZE] = { 0 };
607 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
608 MAKESTRING(IDS_MENUDIFFNOW);
609 CString sMenu;
610 sMenu.Format(CString(stringtablebuffer), compact);
611 wcscpy_s(menutextbuffer, sMenu);
615 MENUITEMINFO menuiteminfo = { 0 };
616 menuiteminfo.cbSize = sizeof(menuiteminfo);
617 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
618 menuiteminfo.fType = MFT_STRING;
619 menuiteminfo.dwTypeData = menutextbuffer;
620 if (icon)
622 menuiteminfo.fMask |= MIIM_BITMAP;
623 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
625 menuiteminfo.wID = (UINT)id;
626 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
628 if (istop)
630 //menu entry for the top context menu, so append an "Git " before
631 //the menu text to indicate where the entry comes from
632 wcscpy_s(menutextbuffer, 255, L"Git ");
634 LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
635 wcscat_s(menutextbuffer, 255, verbsbuffer);
636 auto verb = std::wstring(menutextbuffer);
637 if (verb.find('&') != -1)
639 verb.erase(verb.find('&'),1);
641 myVerbsMap[verb] = id - idCmdFirst;
642 myVerbsMap[verb] = id;
643 myVerbsIDMap[id - idCmdFirst] = verb;
644 myVerbsIDMap[id] = verb;
645 // We store the relative and absolute diameter
646 // (drawitem callback uses absolute, others relative)
647 myIDMap[id - idCmdFirst] = com;
648 myIDMap[id] = com;
649 if (!istop)
650 mySubMenuMap[pos] = com;
653 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring& tempfile)
655 tempfile = std::wstring();
656 //write all selected files and paths to a temporary file
657 //for TortoiseGitProc.exe to read out again.
658 DWORD written = 0;
659 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
660 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
661 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
662 GetTortoiseGitTempPath(pathlength+1, path.get());
663 GetTempFileName(path.get(), L"git", 0, tempFile.get());
664 tempfile = std::wstring(tempFile.get());
666 CAutoFile file = ::CreateFile(tempFile.get(),
667 GENERIC_WRITE,
668 FILE_SHARE_READ,
669 nullptr,
670 CREATE_ALWAYS,
671 FILE_ATTRIBUTE_TEMPORARY,
672 nullptr);
674 if (!file)
675 return false;
677 if (!IsClipboardFormatAvailable(CF_HDROP))
678 return false;
679 if (!OpenClipboard(nullptr))
680 return false;
682 HGLOBAL hglb = GetClipboardData(CF_HDROP);
683 SCOPE_EXIT
685 GlobalUnlock(hglb);
686 CloseClipboard();
688 HDROP hDrop = (HDROP)GlobalLock(hglb);
689 if (!hDrop)
690 return false;
691 SCOPE_EXIT { GlobalUnlock(hDrop); };
693 TCHAR szFileName[MAX_PATH] = {0};
694 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
695 for(UINT i = 0; i < cFiles; ++i)
697 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
698 std::wstring filename = szFileName;
699 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
700 ::WriteFile(file, L"\n", 2, &written, 0);
703 return true;
706 std::wstring CShellExt::WriteFileListToTempFile()
708 //write all selected files and paths to a temporary file
709 //for TortoiseGitProc.exe to read out again.
710 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
711 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
712 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
713 GetTortoiseGitTempPath(pathlength + 1, path.get());
714 GetTempFileName(path.get(), L"git", 0, tempFile.get());
715 auto retFilePath = std::wstring(tempFile.get());
717 CAutoFile file = ::CreateFile (tempFile.get(),
718 GENERIC_WRITE,
719 FILE_SHARE_READ,
720 nullptr,
721 CREATE_ALWAYS,
722 FILE_ATTRIBUTE_TEMPORARY,
723 nullptr);
725 if (!file)
727 MessageBox(nullptr, L"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile.get()), L"TortoiseGit", MB_ICONERROR);
728 return std::wstring();
731 DWORD written = 0;
732 if (files_.empty())
734 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
735 ::WriteFile(file, L"\n", 2, &written, 0);
738 for (const auto& file_ : files_)
740 ::WriteFile(file, file_.c_str(), (DWORD)file_.size() * sizeof(TCHAR), &written, 0);
741 ::WriteFile(file, L"\n", 2, &written, 0);
743 return retFilePath;
746 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
748 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
749 return S_OK;
751 PreserveChdir preserveChdir;
752 LoadLangDll();
754 if ((uFlags & CMF_DEFAULTONLY)!=0)
755 return S_OK; //we don't change the default action
757 if (files_.empty() || folder_.empty())
758 return S_OK;
760 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
761 return S_OK;
763 bool bSourceAndTargetFromSameRepository = ((uuidSource.size() == uuidTarget.size() && _wcsnicmp(uuidSource.c_str(), uuidTarget.c_str(), uuidSource.size()) == 0)) || uuidSource.empty() || uuidTarget.empty();
765 //the drop handler only has eight commands, but not all are visible at the same time:
766 //if the source file(s) are under version control then those files can be moved
767 //to the new location or they can be moved with a rename,
768 //if they are unversioned then they can be added to the working copy
769 //if they are versioned, they also can be exported to an unversioned location
770 UINT idCmd = idCmdFirst;
772 bool moveAvailable = false;
773 // Git move here
774 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
775 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT))))
777 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
778 moveAvailable = true;
781 // Git move and rename here
782 // 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
783 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE))
785 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
786 moveAvailable = true;
789 // Git copy here
790 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
791 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
792 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
794 // Git copy and rename here, source and target from same repository
795 // available if source is a single, versioned but not added item, target is versioned or target folder is added
796 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
797 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
799 // Git add here
800 // available if target is versioned and source is either unversioned or from another repository
801 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
802 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
804 // Git export here
805 // available if source is versioned and a folder
806 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
807 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
809 // Git export all here
810 // available if source is versioned and a folder
811 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
812 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
814 // apply patch
815 // available if source is a patchfile
816 if (itemStates & ITEMIS_PATCHFILE)
818 if (itemStates & ITEMIS_ONLYONE)
819 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
820 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUIMPORTPATCH, 0, idCmdFirst, ShellMenuImportPatchDrop, uFlags);
823 // separator
824 if (idCmd != idCmdFirst)
825 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
827 TweakMenu(hMenu);
829 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
832 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
833 UINT indexMenu,
834 UINT idCmdFirst,
835 UINT /*idCmdLast*/,
836 UINT uFlags)
838 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
839 PreserveChdir preserveChdir;
841 //first check if our drop handler is called
842 //and then (if true) provide the context menu for the
843 //drop handler
844 if (m_State == FileStateDropHandler)
846 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
849 if ((uFlags & CMF_DEFAULTONLY)!=0)
850 return S_OK; //we don't change the default action
852 if (files_.empty() && folder_.empty())
853 return S_OK;
855 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
856 return S_OK;
858 int csidlarray[] =
860 CSIDL_BITBUCKET,
861 CSIDL_CDBURN_AREA,
862 CSIDL_COMMON_FAVORITES,
863 CSIDL_COMMON_STARTMENU,
864 CSIDL_COMPUTERSNEARME,
865 CSIDL_CONNECTIONS,
866 CSIDL_CONTROLS,
867 CSIDL_COOKIES,
868 CSIDL_FAVORITES,
869 CSIDL_FONTS,
870 CSIDL_HISTORY,
871 CSIDL_INTERNET,
872 CSIDL_INTERNET_CACHE,
873 CSIDL_NETHOOD,
874 CSIDL_NETWORK,
875 CSIDL_PRINTERS,
876 CSIDL_PRINTHOOD,
877 CSIDL_RECENT,
878 CSIDL_SENDTO,
879 CSIDL_STARTMENU,
882 if (IsIllegalFolder(folder_, csidlarray))
883 return S_OK;
885 if (folder_.empty())
887 // folder is empty, but maybe files are selected
888 if (files_.empty())
889 return S_OK; // nothing selected - we don't have a menu to show
890 // check whether a selected entry is an UID - those are namespace extensions
891 // which we can't handle
892 for (const auto& file : files_)
894 if (CStringUtils::StartsWith(file.c_str(), L"::{"))
895 return S_OK;
898 else
900 if (CStringUtils::StartsWith(folder_.c_str(), L"::{"))
901 return S_OK;
904 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
906 if ((itemStates & (ITEMIS_INGIT | ITEMIS_INVERSIONEDFOLDER | ITEMIS_FOLDERINGIT | ITEMIS_BAREREPO)) == 0)
907 return S_OK;
910 //check if our menu is requested for a git admin directory
911 if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
912 return S_OK;
914 if (uFlags & CMF_EXTENDEDVERBS)
915 itemStates |= ITEMIS_EXTENDED;
917 regDiffLater.read();
918 if (!std::wstring(regDiffLater).empty())
919 itemStates |= ITEMIS_HASDIFFLATER;
921 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
922 if ( bShortcut && (files_.size()==1))
924 // Don't show the context menu for a link if the
925 // destination is not part of a working copy.
926 // It would only show the standard menu items
927 // which are already shown for the lnk-file.
928 CString path = files_.front().c_str();
929 if (!GitAdminDir::HasAdminDir(path))
931 return S_OK;
935 //check if we already added our menu entry for a folder.
936 //we check that by iterating through all menu entries and check if
937 //the dwItemData member points to our global ID string. That string is set
938 //by our shell extension when the folder menu is inserted.
939 TCHAR menubuf[MAX_PATH] = {0};
940 int count = GetMenuItemCount(hMenu);
941 for (int i=0; i<count; ++i)
943 MENUITEMINFO miif = { 0 };
944 miif.cbSize = sizeof(MENUITEMINFO);
945 miif.fMask = MIIM_DATA;
946 miif.dwTypeData = menubuf;
947 miif.cch = _countof(menubuf);
948 GetMenuItemInfo(hMenu, i, TRUE, &miif);
949 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
950 return S_OK;
953 LoadLangDll();
954 UINT idCmd = idCmdFirst;
956 //create the sub menu
957 HMENU subMenu = CreateMenu();
958 int indexSubMenu = 0;
960 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
961 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
962 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
964 int menuIndex = 0;
965 bool bAddSeparator = false;
966 bool bMenuEntryAdded = false;
967 bool bMenuEmpty = true;
968 // insert separator at start
969 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
970 bool bShowIcons = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY));
972 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
974 if (menuInfo[menuIndex].command == ShellSeparator)
976 // we don't add a separator immediately. Because there might not be
977 // another 'normal' menu entry after we insert a separator.
978 // we simply set a flag here, indicating that before the next
979 // 'normal' menu entry, a separator should be added.
980 if (!bMenuEmpty)
981 bAddSeparator = true;
982 if (bMenuEntryAdded)
983 bAddSeparator = true;
985 else
987 // check the conditions whether to show the menu entry or not
988 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
989 if (menuInfo[menuIndex].menuID & menuex)
991 if( !(itemStates & ITEMIS_EXTENDED) )
992 bInsertMenu = false;
995 if (menuInfo[menuIndex].menuID & (~menumask))
997 if (bInsertMenu)
999 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1000 // insert a separator
1001 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1003 bAddSeparator = false;
1004 bMenuEntryAdded = false;
1005 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, nullptr);
1006 idCmd++;
1009 // handle special cases (sub menus)
1010 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1012 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1013 bMenuEntryAdded = true;
1015 else
1017 bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1019 // insert the menu entry
1020 InsertGitMenu( bIsTop,
1021 bIsTop ? hMenu : subMenu,
1022 bIsTop ? indexMenu++ : indexSubMenu++,
1023 idCmd++,
1024 menuInfo[menuIndex].menuTextID,
1025 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1026 idCmdFirst,
1027 menuInfo[menuIndex].command,
1028 uFlags);
1029 if (!bIsTop)
1031 bMenuEntryAdded = true;
1032 bMenuEmpty = false;
1033 bAddSeparator = false;
1039 menuIndex++;
1042 // do not show TortoiseGit menu if it's empty
1043 if (bMenuEmpty)
1045 if (idCmd - idCmdFirst > 0)
1047 //separator after
1048 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1050 TweakMenu(hMenu);
1052 //return number of menu items added
1053 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1056 //add sub menu to main context menu
1057 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1058 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1059 MAKESTRING(IDS_MENUSUBMENU);
1060 if (!g_ShellCache.HasShellMenuAccelerators())
1062 // remove the accelerators
1063 tstring temp = stringtablebuffer;
1064 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1065 wcscpy_s(stringtablebuffer, temp.c_str());
1067 MENUITEMINFO menuiteminfo = { 0 };
1068 menuiteminfo.cbSize = sizeof(menuiteminfo);
1069 menuiteminfo.fType = MFT_STRING;
1070 menuiteminfo.dwTypeData = stringtablebuffer;
1072 UINT uIcon = bShowIcons ? IDI_APP : 0;
1073 if (!folder_.empty())
1075 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1076 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1077 myIDMap[idCmd] = ShellSubMenuFolder;
1078 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1080 else if (!bShortcut && (files_.size()==1))
1082 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1083 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1084 myIDMap[idCmd] = ShellSubMenuFile;
1086 else if (bShortcut && (files_.size()==1))
1088 uIcon = bShowIcons ? IDI_MENULINK : 0;
1089 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1090 myIDMap[idCmd] = ShellSubMenuLink;
1092 else if (!files_.empty())
1094 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1095 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1096 myIDMap[idCmd] = ShellSubMenuMultiple;
1098 else
1100 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1101 myIDMap[idCmd] = ShellSubMenu;
1103 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1104 if (uIcon)
1106 menuiteminfo.fMask |= MIIM_BITMAP;
1107 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon);
1109 menuiteminfo.hSubMenu = subMenu;
1110 menuiteminfo.wID = idCmd++;
1111 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1113 //separator after
1114 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1116 TweakMenu(hMenu);
1118 //return number of menu items added
1119 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1122 void CShellExt::TweakMenu(HMENU hMenu)
1124 MENUINFO MenuInfo = {};
1125 MenuInfo.cbSize = sizeof(MenuInfo);
1126 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1127 MenuInfo.dwStyle = MNS_CHECKORBMP;
1128 SetMenuInfo(hMenu, &MenuInfo);
1131 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1133 gitCmd += command;
1134 gitCmd += L" /path:\"";
1135 if ((bFilesAllowed) && !files_.empty())
1136 gitCmd += files_.front();
1137 else
1138 gitCmd += folder_;
1139 gitCmd += L'"';
1142 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1144 tstring tempfile = WriteFileListToTempFile();
1145 gitCmd += command;
1146 gitCmd += L" /pathfile:\"";
1147 gitCmd += tempfile;
1148 gitCmd += L'"';
1149 gitCmd += L" /deletepathfile";
1152 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1154 tstring tempfile = WriteFileListToTempFile();
1155 gitCmd += command;
1156 gitCmd += L" /pathfile:\"";
1157 gitCmd += tempfile;
1158 gitCmd += L'"';
1159 gitCmd += L" /deletepathfile";
1160 gitCmd += L" /droptarget:\"";
1161 gitCmd += folder_;
1162 gitCmd += L'"';
1165 // This is called when you invoke a command on the menu:
1166 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1168 PreserveChdir preserveChdir;
1169 HRESULT hr = E_INVALIDARG;
1170 if (!lpcmi)
1171 return hr;
1173 if (!files_.empty() || !folder_.empty())
1175 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1177 if (HIWORD(lpcmi->lpVerb))
1179 auto verb = std::wstring(MultibyteToWide(lpcmi->lpVerb));
1180 const auto verb_it = myVerbsMap.lower_bound(verb);
1181 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1182 idCmd = verb_it->second;
1183 else
1184 return hr;
1187 // See if we have a handler interface for this id
1188 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1189 if (id_it != myIDMap.end() && id_it->first == idCmd)
1191 tstring tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitProc.exe");
1192 tstring tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitMerge.exe");
1194 //TortoiseGitProc expects a command line of the form:
1195 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1196 // or
1197 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1199 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1200 //* pathfile is a path to a temporary file which contains a list of file paths
1201 std::wstring gitCmd = L" /command:";
1202 std::wstring tempfile;
1203 switch (id_it->second)
1205 //#region case
1206 case ShellMenuSync:
1208 TCHAR syncSeq[12] = { 0 };
1209 swprintf_s(syncSeq, L"%d", g_syncSeq++);
1210 AddPathCommand(gitCmd, L"sync", false);
1211 gitCmd += L" /seq:";
1212 gitCmd += syncSeq;
1214 break;
1215 case ShellMenuSubSync:
1216 AddPathFileCommand(gitCmd, L"subsync");
1217 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1219 gitCmd += L" /bkpath:\"";
1220 gitCmd += folder_;
1221 gitCmd += L'"';
1223 break;
1224 case ShellMenuUpdateExt:
1225 AddPathFileCommand(gitCmd, L"subupdate");
1226 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1228 gitCmd += L" /bkpath:\"";
1229 gitCmd += folder_;
1230 gitCmd += L'"';
1232 break;
1233 case ShellMenuCommit:
1234 AddPathFileCommand(gitCmd, L"commit");
1235 break;
1236 case ShellMenuAdd:
1237 AddPathFileCommand(gitCmd, L"add");
1238 break;
1239 case ShellMenuIgnore:
1240 AddPathFileCommand(gitCmd, L"ignore");
1241 break;
1242 case ShellMenuIgnoreCaseSensitive:
1243 AddPathFileCommand(gitCmd, L"ignore");
1244 gitCmd += L" /onlymask";
1245 break;
1246 case ShellMenuDeleteIgnore:
1247 AddPathFileCommand(gitCmd, L"ignore");
1248 gitCmd += L" /delete";
1249 break;
1250 case ShellMenuDeleteIgnoreCaseSensitive:
1251 AddPathFileCommand(gitCmd, L"ignore");
1252 gitCmd += L" /delete /onlymask";
1253 break;
1254 case ShellMenuUnIgnore:
1255 AddPathFileCommand(gitCmd, L"unignore");
1256 break;
1257 case ShellMenuUnIgnoreCaseSensitive:
1258 AddPathFileCommand(gitCmd, L"unignore");
1259 gitCmd += L" /onlymask";
1260 break;
1261 case ShellMenuMergeAbort:
1262 AddPathCommand(gitCmd, L"merge", false);
1263 gitCmd += L" /abort";
1264 break;
1265 case ShellMenuRevert:
1266 AddPathFileCommand(gitCmd, L"revert");
1267 break;
1268 case ShellMenuCleanup:
1269 AddPathFileCommand(gitCmd, L"cleanup");
1270 break;
1271 case ShellMenuSendMail:
1272 AddPathFileCommand(gitCmd, L"sendmail");
1273 break;
1274 case ShellMenuResolve:
1275 AddPathFileCommand(gitCmd, L"resolve");
1276 break;
1277 case ShellMenuSwitch:
1278 AddPathCommand(gitCmd, L"switch", false);
1279 break;
1280 case ShellMenuExport:
1281 AddPathCommand(gitCmd, L"export", false);
1282 break;
1283 case ShellMenuAbout:
1284 gitCmd += L"about";
1285 break;
1286 case ShellMenuCreateRepos:
1287 AddPathCommand(gitCmd, L"repocreate", false);
1288 break;
1289 case ShellMenuMerge:
1290 AddPathCommand(gitCmd, L"merge", false);
1291 break;
1292 case ShellMenuCopy:
1293 AddPathCommand(gitCmd, L"copy", true);
1294 break;
1295 case ShellMenuSettings:
1296 AddPathCommand(gitCmd, L"settings", true);
1297 break;
1298 case ShellMenuHelp:
1299 gitCmd += L"help";
1300 break;
1301 case ShellMenuRename:
1302 AddPathCommand(gitCmd, L"rename", true);
1303 break;
1304 case ShellMenuRemove:
1305 AddPathFileCommand(gitCmd, L"remove");
1306 if (itemStates & ITEMIS_SUBMODULE)
1307 gitCmd += L" /submodule";
1308 break;
1309 case ShellMenuRemoveKeep:
1310 AddPathFileCommand(gitCmd, L"remove");
1311 gitCmd += L" /keep";
1312 break;
1313 case ShellMenuDiff:
1314 gitCmd += L"diff /path:\"";
1315 if (files_.size() == 1)
1316 gitCmd += files_.front();
1317 else if (files_.size() == 2)
1319 auto I = files_.cbegin();
1320 gitCmd += *I;
1321 ++I;
1322 gitCmd += L"\" /path2:\"";
1323 gitCmd += *I;
1325 else
1326 gitCmd += folder_;
1327 gitCmd += L'"';
1328 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1329 gitCmd += L" /alternative";
1330 break;
1331 case ShellMenuDiffLater:
1332 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1334 gitCmd.clear();
1335 regDiffLater.removeValue();
1337 else if (files_.size() == 1)
1339 if (std::wstring(regDiffLater).empty())
1341 gitCmd.clear();
1342 regDiffLater = files_[0];
1344 else
1346 AddPathCommand(gitCmd, L"diff", true);
1347 gitCmd += L" /path2:\"";
1348 gitCmd += std::wstring(regDiffLater);
1349 gitCmd += L'"';
1350 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1351 gitCmd += L" /alternative";
1352 regDiffLater.removeValue();
1355 else
1356 gitCmd.clear();
1357 break;
1358 case ShellMenuPrevDiff:
1359 AddPathCommand(gitCmd, L"prevdiff", true);
1360 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1361 gitCmd += L" /alternative";
1362 break;
1363 case ShellMenuDiffTwo:
1364 AddPathCommand(gitCmd, L"diffcommits", true);
1365 break;
1366 case ShellMenuDropCopyAdd:
1367 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1368 break;
1369 case ShellMenuDropCopy:
1370 AddPathFileDropCommand(gitCmd, L"dropcopy");
1371 break;
1372 case ShellMenuDropCopyRename:
1373 AddPathFileDropCommand(gitCmd, L"dropcopy");
1374 gitCmd += L" /rename";
1375 break;
1376 case ShellMenuDropMove:
1377 AddPathFileDropCommand(gitCmd, L"dropmove");
1378 break;
1379 case ShellMenuDropMoveRename:
1380 AddPathFileDropCommand(gitCmd, L"dropmove");
1381 gitCmd += L" /rename";
1382 break;
1383 case ShellMenuDropExport:
1384 AddPathFileDropCommand(gitCmd, L"dropexport");
1385 break;
1386 case ShellMenuDropExportExtended:
1387 AddPathFileDropCommand(gitCmd, L"dropexport");
1388 gitCmd += L" /extended";
1389 break;
1390 case ShellMenuLog:
1391 case ShellMenuLogSubmoduleFolder:
1392 AddPathCommand(gitCmd, L"log", true);
1393 if (id_it->second == ShellMenuLogSubmoduleFolder)
1394 gitCmd += L" /submodule";
1395 break;
1396 case ShellMenuDaemon:
1397 AddPathCommand(gitCmd, L"daemon", true);
1398 break;
1399 case ShellMenuRevisionGraph:
1400 AddPathCommand(gitCmd, L"revisiongraph", true);
1401 break;
1402 case ShellMenuConflictEditor:
1403 AddPathCommand(gitCmd, L"conflicteditor", true);
1404 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1405 gitCmd += L" /alternative";
1406 break;
1407 case ShellMenuGitSVNRebase:
1408 AddPathCommand(gitCmd, L"svnrebase", false);
1409 break;
1410 case ShellMenuGitSVNDCommit:
1411 AddPathCommand(gitCmd, L"svndcommit", true);
1412 break;
1413 case ShellMenuGitSVNDFetch:
1414 AddPathCommand(gitCmd, L"svnfetch", false);
1415 break;
1416 case ShellMenuGitSVNIgnore:
1417 AddPathCommand(gitCmd, L"svnignore", false);
1418 break;
1419 case ShellMenuRebase:
1420 AddPathCommand(gitCmd, L"rebase", false);
1421 break;
1422 case ShellMenuShowChanged:
1423 if (files_.size() > 1)
1424 AddPathFileCommand(gitCmd, L"repostatus");
1425 else
1426 AddPathCommand(gitCmd, L"repostatus", true);
1427 break;
1428 case ShellMenuRepoBrowse:
1429 AddPathCommand(gitCmd, L"repobrowser", false);
1430 break;
1431 case ShellMenuRefBrowse:
1432 AddPathCommand(gitCmd, L"refbrowse", false);
1433 break;
1434 case ShellMenuRefLog:
1435 AddPathCommand(gitCmd, L"reflog", false);
1436 break;
1437 case ShellMenuStashSave:
1438 AddPathCommand(gitCmd, L"stashsave", true);
1439 break;
1440 case ShellMenuStashApply:
1441 AddPathCommand(gitCmd, L"stashapply", false);
1442 break;
1443 case ShellMenuStashPop:
1444 AddPathCommand(gitCmd, L"stashpop", false);
1445 break;
1446 case ShellMenuStashList:
1447 AddPathCommand(gitCmd, L"reflog", false);
1448 gitCmd += L" /ref:refs/stash";
1449 break;
1450 case ShellMenuBisectStart:
1451 AddPathCommand(gitCmd, L"bisect", false);
1452 gitCmd += L" /start";
1453 break;
1454 case ShellMenuBisectGood:
1455 AddPathCommand(gitCmd, L"bisect", false);
1456 gitCmd += L" /good";
1457 break;
1458 case ShellMenuBisectBad:
1459 AddPathCommand(gitCmd, L"bisect", false);
1460 gitCmd += L" /bad";
1461 break;
1462 case ShellMenuBisectSkip:
1463 AddPathCommand(gitCmd, L"bisect", false);
1464 gitCmd += L" /skip";
1465 break;
1466 case ShellMenuBisectReset:
1467 AddPathCommand(gitCmd, L"bisect", false);
1468 gitCmd += L" /reset";
1469 break;
1470 case ShellMenuSubAdd:
1471 AddPathCommand(gitCmd, L"subadd", false);
1472 break;
1473 case ShellMenuBlame:
1474 AddPathCommand(gitCmd, L"blame", true);
1475 break;
1476 case ShellMenuApplyPatch:
1477 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1479 // if there's a patch file in the clipboard, we save it
1480 // to a temporary file and tell TortoiseGitMerge to use that one
1481 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
1482 if (cFormat && OpenClipboard(nullptr))
1484 HGLOBAL hglb = GetClipboardData(cFormat);
1485 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1487 DWORD len = GetTortoiseGitTempPath(0, nullptr);
1488 auto path = std::make_unique<TCHAR[]>(len + 1);
1489 auto tempF = std::make_unique<TCHAR[]>(len + 100);
1490 GetTortoiseGitTempPath(len + 1, path.get());
1491 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1492 std::wstring sTempFile = std::wstring(tempF.get());
1494 FILE * outFile;
1495 size_t patchlen = strlen(lpstr);
1496 _wfopen_s(&outFile, sTempFile.c_str(), L"wb");
1497 if(outFile)
1499 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1500 if (size == patchlen)
1502 itemStates |= ITEMIS_PATCHFILE;
1503 files_.clear();
1504 files_.push_back(sTempFile);
1506 fclose(outFile);
1508 GlobalUnlock(hglb);
1509 CloseClipboard();
1512 if (itemStates & ITEMIS_PATCHFILE)
1514 gitCmd = L" /diff:\"";
1515 if (!files_.empty())
1517 gitCmd += files_.front();
1518 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1520 gitCmd += L"\" /patchpath:\"";
1521 gitCmd += folder_;
1524 else
1525 gitCmd += folder_;
1526 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1527 gitCmd += L"\" /wc";
1528 else
1529 gitCmd += L'"';
1531 else
1533 gitCmd = L" /patchpath:\"";
1534 if (!files_.empty())
1535 gitCmd += files_.front();
1536 else
1537 gitCmd += folder_;
1538 gitCmd += L'"';
1540 myIDMap.clear();
1541 myVerbsIDMap.clear();
1542 myVerbsMap.clear();
1543 RunCommand(tortoiseMergePath, gitCmd, L"TortoiseGitMerge launch failed");
1544 return S_OK;
1545 break;
1546 case ShellMenuClipPaste:
1547 if (WriteClipboardPathsToTempFile(tempfile))
1549 bool bCopy = true;
1550 UINT cPrefDropFormat = RegisterClipboardFormat(L"Preferred DropEffect");
1551 if (cPrefDropFormat)
1553 if (OpenClipboard(lpcmi->hwnd))
1555 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1556 if (hglb)
1558 DWORD* effect = (DWORD*) GlobalLock(hglb);
1559 if (*effect == DROPEFFECT_MOVE)
1560 bCopy = false;
1561 GlobalUnlock(hglb);
1563 CloseClipboard();
1567 if (bCopy)
1568 gitCmd += L"pastecopy /pathfile:\"";
1569 else
1570 gitCmd += L"pastemove /pathfile:\"";
1571 gitCmd += tempfile;
1572 gitCmd += L'"';
1573 gitCmd += L" /deletepathfile";
1574 gitCmd += L" /droptarget:\"";
1575 gitCmd += folder_;
1576 gitCmd += L'"';
1578 else return S_OK;
1579 break;
1580 case ShellMenuClone:
1581 AddPathCommand(gitCmd, L"clone", false);
1582 break;
1583 case ShellMenuPull:
1584 AddPathCommand(gitCmd, L"pull", false);
1585 break;
1586 case ShellMenuPush:
1587 AddPathCommand(gitCmd, L"push", false);
1588 break;
1589 case ShellMenuBranch:
1590 AddPathCommand(gitCmd, L"branch", false);
1591 break;
1592 case ShellMenuTag:
1593 AddPathCommand(gitCmd, L"tag", false);
1594 break;
1595 case ShellMenuFormatPatch:
1596 AddPathCommand(gitCmd, L"formatpatch", false);
1597 break;
1598 case ShellMenuImportPatch:
1599 AddPathFileCommand(gitCmd, L"importpatch");
1600 break;
1601 case ShellMenuImportPatchDrop:
1602 AddPathFileDropCommand(gitCmd, L"importpatch");
1603 break;
1604 case ShellMenuFetch:
1605 AddPathCommand(gitCmd, L"fetch", false);
1606 break;
1608 default:
1609 break;
1610 //#endregion
1611 } // switch (id_it->second)
1612 if (!gitCmd.empty())
1614 gitCmd += L" /hwnd:";
1615 TCHAR buf[30] = { 0 };
1616 swprintf_s(buf, L"%p", (void*)lpcmi->hwnd);
1617 gitCmd += buf;
1618 myIDMap.clear();
1619 myVerbsIDMap.clear();
1620 myVerbsMap.clear();
1621 RunCommand(tortoiseProcPath, gitCmd, L"TortoiseProc launch failed");
1623 hr = S_OK;
1624 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1625 } // if (files_.empty() || folder_.empty())
1626 return hr;
1629 // This is for the status bar and things like that:
1630 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1631 UINT uFlags,
1632 UINT FAR * /*reserved*/,
1633 LPSTR pszName,
1634 UINT cchMax)
1636 PreserveChdir preserveChdir;
1637 //do we know the id?
1638 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1639 if (id_it == myIDMap.end() || id_it->first != idCmd)
1641 return E_INVALIDARG; //no, we don't
1644 LoadLangDll();
1645 HRESULT hr = E_INVALIDARG;
1647 MAKESTRING(IDS_MENUDESCDEFAULT);
1648 int menuIndex = 0;
1649 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1651 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1653 MAKESTRING(menuInfo[menuIndex].menuDescID);
1654 break;
1656 menuIndex++;
1659 const TCHAR * desc = stringtablebuffer;
1660 switch(uFlags)
1662 case GCS_HELPTEXTA:
1664 std::string help = WideToMultibyte(desc);
1665 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1666 hr = S_OK;
1667 break;
1669 case GCS_HELPTEXTW:
1671 std::wstring help = desc;
1672 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1673 hr = S_OK;
1674 break;
1676 case GCS_VERBA:
1678 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1679 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1681 std::string help = WideToMultibyte(verb_id_it->second);
1682 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1683 hr = S_OK;
1686 break;
1687 case GCS_VERBW:
1689 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1690 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1692 std::wstring help = verb_id_it->second;
1693 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1694 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1695 hr = S_OK;
1698 break;
1700 return hr;
1703 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1705 LRESULT res;
1706 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1709 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1711 PreserveChdir preserveChdir;
1713 LRESULT res;
1714 if (!pResult)
1715 pResult = &res;
1716 *pResult = FALSE;
1718 LoadLangDll();
1719 switch (uMsg)
1721 case WM_MEASUREITEM:
1723 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1724 if (!lpmis)
1725 break;
1726 lpmis->itemWidth = 16;
1727 lpmis->itemHeight = 16;
1728 *pResult = TRUE;
1730 break;
1731 case WM_DRAWITEM:
1733 LPCTSTR resource;
1734 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1735 if (!lpdis || lpdis->CtlType != ODT_MENU)
1736 return S_OK; //not for a menu
1737 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1738 if (!resource)
1739 return S_OK;
1740 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1741 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1742 HICON hIcon = (HICON)LoadImage(g_hResInst, resource, IMAGE_ICON, iconWidth, iconHeight, LR_DEFAULTCOLOR);
1743 if (!hIcon)
1744 return S_OK;
1745 DrawIconEx(lpdis->hDC,
1746 lpdis->rcItem.left,
1747 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - iconHeight) / 2,
1748 hIcon, iconWidth, iconHeight,
1749 0, nullptr, DI_NORMAL);
1750 DestroyIcon(hIcon);
1751 *pResult = TRUE;
1753 break;
1754 case WM_MENUCHAR:
1756 TCHAR *szItem;
1757 if (HIWORD(wParam) != MF_POPUP)
1758 return S_OK;
1759 int nChar = LOWORD(wParam);
1760 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1761 nChar = tolower(nChar);
1762 // we have the char the user pressed, now search that char in all our
1763 // menu items
1764 std::vector<UINT_PTR> accmenus;
1765 for (auto It = mySubMenuMap.cbegin(); It != mySubMenuMap.cend(); ++It)
1767 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1768 if (!resource)
1769 continue;
1770 szItem = stringtablebuffer;
1771 TCHAR* amp = wcschr(szItem, L'&');
1772 if (!amp)
1773 continue;
1774 amp++;
1775 int ampChar = LOWORD(*amp);
1776 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1777 ampChar = tolower(ampChar);
1778 if (ampChar == nChar)
1780 // yep, we found a menu which has the pressed key
1781 // as an accelerator. Add that menu to the list to
1782 // process later.
1783 accmenus.push_back(It->first);
1786 if (accmenus.empty())
1788 // no menu with that accelerator key.
1789 *pResult = MAKELONG(0, MNC_IGNORE);
1790 return S_OK;
1792 if (accmenus.size() == 1)
1794 // Only one menu with that accelerator key. We're lucky!
1795 // So just execute that menu entry.
1796 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1797 return S_OK;
1799 if (accmenus.size() > 1)
1801 // we have more than one menu item with this accelerator key!
1802 MENUITEMINFO mif;
1803 mif.cbSize = sizeof(MENUITEMINFO);
1804 mif.fMask = MIIM_STATE;
1805 for (auto it = accmenus.cbegin(); it != accmenus.cend(); ++it)
1807 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1808 if (mif.fState == MFS_HILITE)
1810 // this is the selected item, so select the next one
1811 ++it;
1812 if (it == accmenus.end())
1813 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1814 else
1815 *pResult = MAKELONG(*it, MNC_SELECT);
1816 return S_OK;
1819 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1822 break;
1823 default:
1824 return S_OK;
1827 return S_OK;
1830 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1832 TCHAR textbuf[255] = { 0 };
1833 LPCTSTR resource = nullptr;
1834 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1835 space = 6;
1837 int menuIndex = 0;
1838 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1840 if (menuInfo[menuIndex].command == id)
1842 MAKESTRING(menuInfo[menuIndex].menuTextID);
1843 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1844 switch (id)
1846 case ShellSubMenuMultiple:
1847 case ShellSubMenuLink:
1848 case ShellSubMenuFolder:
1849 case ShellSubMenuFile:
1850 case ShellSubMenu:
1851 space = 0;
1852 break;
1853 default:
1854 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1855 if (layout & menuInfo[menuIndex].menuID)
1857 wcscpy_s(textbuf, 255, L"Git ");
1858 wcscat_s(textbuf, 255, stringtablebuffer);
1859 wcscpy_s(stringtablebuffer, 255, textbuf);
1861 break;
1863 return resource;
1865 menuIndex++;
1867 return nullptr;
1870 bool CShellExt::IsIllegalFolder(const std::wstring& folder, int* cslidarray)
1872 int i=0;
1873 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1874 LPITEMIDLIST pidl = nullptr;
1875 while (cslidarray[i])
1877 ++i;
1878 pidl = nullptr;
1879 if (SHGetFolderLocation(nullptr, cslidarray[i - 1], nullptr, 0, &pidl) != S_OK)
1880 continue;
1881 if (!SHGetPathFromIDList(pidl, buf))
1883 // not a file system path, definitely illegal for our use
1884 CoTaskMemFree(pidl);
1885 continue;
1887 CoTaskMemFree(pidl);
1888 if (!buf[0])
1889 continue;
1890 if (wcscmp(buf, folder.c_str()) == 0)
1891 return true;
1893 return false;
1896 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1898 HMENU ignoresubmenu = nullptr;
1899 int indexignoresub = 0;
1900 bool bShowIgnoreMenu = false;
1901 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1902 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1903 if (files_.empty())
1904 return false;
1905 UINT icon = bShowIcons ? IDI_IGNORE : 0;
1907 auto I = files_.cbegin();
1908 if (wcsrchr(I->c_str(), L'\\'))
1909 wcscpy_s(ignorepath, wcsrchr(I->c_str(), L'\\') + 1);
1910 else
1911 wcscpy_s(ignorepath, I->c_str());
1912 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
1914 // check if the item name is ignored or the mask
1915 size_t p = 0;
1916 const size_t pathLength = wcslen(ignorepath);
1917 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
1919 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
1920 && (p + pathLength == ignoredprops.length() || ignoredprops[p + pathLength + 1] == TCHAR('\n')))
1922 break;
1924 p++;
1926 if (p!=-1)
1928 ignoresubmenu = CreateMenu();
1929 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1930 auto verb = std::wstring(ignorepath);
1931 myVerbsMap[verb] = idCmd - idCmdFirst;
1932 myVerbsMap[verb] = idCmd;
1933 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1934 myVerbsIDMap[idCmd] = verb;
1935 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
1936 myIDMap[idCmd++] = ShellMenuUnIgnore;
1937 bShowIgnoreMenu = true;
1939 wcscpy_s(maskbuf, L"*");
1940 if (wcsrchr(ignorepath, L'.'))
1942 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
1943 p = ignoredprops.find(maskbuf);
1944 if ((p!=-1) &&
1945 ((ignoredprops.compare(maskbuf) == 0) || (ignoredprops.find(L'\n', p) == p + wcslen(maskbuf) + 1) || (ignoredprops.rfind(L'\n', p) == p - 1)))
1947 if (!ignoresubmenu)
1948 ignoresubmenu = CreateMenu();
1950 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
1951 auto verb = std::wstring(maskbuf);
1952 myVerbsMap[verb] = idCmd - idCmdFirst;
1953 myVerbsMap[verb] = idCmd;
1954 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1955 myVerbsIDMap[idCmd] = verb;
1956 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
1957 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
1958 bShowIgnoreMenu = true;
1962 else if ((itemStates & ITEMIS_IGNORED) == 0)
1964 bShowIgnoreMenu = true;
1965 ignoresubmenu = CreateMenu();
1966 if (itemStates & ITEMIS_ONLYONE)
1968 if (itemStates & ITEMIS_INGIT)
1970 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1971 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
1972 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
1974 wcscpy_s(maskbuf, L"*");
1975 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
1977 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
1978 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
1979 auto verb = std::wstring(maskbuf);
1980 myVerbsMap[verb] = idCmd - idCmdFirst;
1981 myVerbsMap[verb] = idCmd;
1982 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1983 myVerbsIDMap[idCmd] = verb;
1984 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
1985 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
1988 else
1990 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1991 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
1992 myIDMap[idCmd++] = ShellMenuIgnore;
1994 wcscpy_s(maskbuf, L"*");
1995 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
1997 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
1998 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
1999 auto verb = std::wstring(maskbuf);
2000 myVerbsMap[verb] = idCmd - idCmdFirst;
2001 myVerbsMap[verb] = idCmd;
2002 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2003 myVerbsIDMap[idCmd] = verb;
2004 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2005 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2009 else
2011 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2012 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2013 // has selected more than 16 files, we won't know about that here.
2014 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2015 if (itemStates & ITEMIS_INGIT)
2017 if (files_.size() >= 16)
2019 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2);
2020 wcscpy_s(ignorepath, stringtablebuffer);
2022 else
2024 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2025 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2027 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2028 auto verb = std::wstring(ignorepath);
2029 myVerbsMap[verb] = idCmd - idCmdFirst;
2030 myVerbsMap[verb] = idCmd;
2031 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2032 myVerbsIDMap[idCmd] = verb;
2033 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2034 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2036 if (files_.size() >= 16)
2038 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2);
2039 wcscpy_s(ignorepath, stringtablebuffer);
2041 else
2043 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2044 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2046 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2047 verb = std::wstring(ignorepath);
2048 myVerbsMap[verb] = idCmd - idCmdFirst;
2049 myVerbsMap[verb] = idCmd;
2050 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2051 myVerbsIDMap[idCmd] = verb;
2052 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2053 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2055 else
2057 if (files_.size() >= 16)
2059 MAKESTRING(IDS_MENUIGNOREMULTIPLE2);
2060 wcscpy_s(ignorepath, stringtablebuffer);
2062 else
2064 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2065 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2067 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2068 auto verb = std::wstring(ignorepath);
2069 myVerbsMap[verb] = idCmd - idCmdFirst;
2070 myVerbsMap[verb] = idCmd;
2071 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2072 myVerbsIDMap[idCmd] = verb;
2073 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2074 myIDMap[idCmd++] = ShellMenuIgnore;
2076 if (files_.size() >= 16)
2078 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2);
2079 wcscpy_s(ignorepath, stringtablebuffer);
2081 else
2083 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2084 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2086 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2087 verb = std::wstring(ignorepath);
2088 myVerbsMap[verb] = idCmd - idCmdFirst;
2089 myVerbsMap[verb] = idCmd;
2090 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2091 myVerbsIDMap[idCmd] = verb;
2092 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2093 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2098 if (bShowIgnoreMenu)
2100 MENUITEMINFO menuiteminfo = { 0 };
2101 menuiteminfo.cbSize = sizeof(menuiteminfo);
2102 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2103 if (icon)
2105 menuiteminfo.fMask |= MIIM_BITMAP;
2106 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
2108 menuiteminfo.fType = MFT_STRING;
2109 menuiteminfo.hSubMenu = ignoresubmenu;
2110 menuiteminfo.wID = idCmd;
2111 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2112 if (itemStates & ITEMIS_IGNORED)
2113 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2114 else if (itemStates & ITEMIS_INGIT)
2115 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2116 else
2117 GetMenuTextFromResource(ShellMenuIgnoreSub);
2118 menuiteminfo.dwTypeData = stringtablebuffer;
2119 menuiteminfo.cch = (UINT)min(wcslen(menuiteminfo.dwTypeData), UINT_MAX);
2121 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2122 if (itemStates & ITEMIS_IGNORED)
2124 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2125 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2127 else
2129 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2130 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2133 return bShowIgnoreMenu;
2136 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2138 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), command.c_str()))
2140 // process started - exit
2141 return;
2144 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONERROR);
2147 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2149 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2150 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2153 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2155 if (pair.yes && pair.no)
2157 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2158 return true;
2160 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2161 return true;
2162 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2163 return true;
2164 return false;