Show bare repo context menu when opened in folder background
[TortoiseGit.git] / src / TortoiseShell / ContextMenu.cpp
blob45542112ec664c8947c77fcfe200db55cfc040e0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016, 2018 - TortoiseSVN
4 // Copyright (C) 2008-2018 - 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"
33 #include "LoadIconEx.h"
35 #define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
36 #define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
38 int g_shellidlist=RegisterClipboardFormat(CFSTR_SHELLIDLIST);
40 extern MenuInfo menuInfo[];
41 static int g_syncSeq = 0;
43 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST pIDFolder,
44 LPDATAOBJECT pDataObj,
45 HKEY /* hRegKey */)
47 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: Initialize\n");
48 PreserveChdir preserveChdir;
49 files_.clear();
50 folder_.clear();
51 uuidSource.clear();
52 uuidTarget.clear();
53 itemStates = 0;
54 itemStatesFolder = 0;
55 std::wstring statuspath;
56 git_wc_status_kind fetchedstatus = git_wc_status_none;
57 // get selected files/folders
58 if (pDataObj)
60 STGMEDIUM medium;
61 FORMATETC fmte = {(CLIPFORMAT)g_shellidlist,
62 nullptr,
63 DVASPECT_CONTENT,
64 -1,
65 TYMED_HGLOBAL};
66 HRESULT hres = pDataObj->GetData(&fmte, &medium);
68 if (SUCCEEDED(hres) && medium.hGlobal)
70 if (m_State == FileStateDropHandler)
72 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
74 ReleaseStgMedium(&medium);
75 return S_OK;
78 FORMATETC etc = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
79 STGMEDIUM stg = { TYMED_HGLOBAL };
80 if ( FAILED( pDataObj->GetData ( &etc, &stg )))
82 ReleaseStgMedium ( &medium );
83 return E_INVALIDARG;
87 HDROP drop = (HDROP)GlobalLock(stg.hGlobal);
88 if (!drop)
90 ReleaseStgMedium ( &stg );
91 ReleaseStgMedium ( &medium );
92 return E_INVALIDARG;
95 int count = DragQueryFile(drop, (UINT)-1, nullptr, 0);
96 if (count == 1)
97 itemStates |= ITEMIS_ONLYONE;
98 for (int i = 0; i < count; i++)
100 // find the path length in chars
101 UINT len = DragQueryFile(drop, i, nullptr, 0);
102 if (len == 0)
103 continue;
104 auto szFileName = std::make_unique<TCHAR[]>(len + 1);
105 if (0 == DragQueryFile(drop, i, szFileName.get(), len + 1))
106 continue;
107 auto str = std::wstring(szFileName.get());
108 if ((!str.empty()) && (g_ShellCache.IsContextPathAllowed(szFileName.get())))
111 CTGitPath strpath;
112 strpath.SetFromWin(str.c_str());
113 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
114 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
116 files_.push_back(str);
117 if (i == 0)
119 //get the git status of the item
120 git_wc_status_kind status = git_wc_status_none;
121 CTGitPath askedpath;
122 askedpath.SetFromWin(str.c_str());
123 CString workTreePath;
124 askedpath.HasAdminDir(&workTreePath);
125 uuidSource = workTreePath;
128 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
130 CTGitPath tpath(str.c_str());
131 if (!tpath.HasAdminDir())
133 status = git_wc_status_none;
134 continue;
136 if (tpath.IsAdminDir())
138 status = git_wc_status_none;
139 continue;
141 TGITCacheResponse itemStatus;
142 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
143 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
145 fetchedstatus = status = (git_wc_status_kind)itemStatus.m_status;
146 if (askedpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
148 itemStates |= ITEMIS_FOLDER;
149 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
150 itemStates |= ITEMIS_FOLDERINGIT;
154 else
156 GitStatus stat;
157 stat.GetStatus(CTGitPath(str.c_str()), false, false, true);
158 if (stat.status)
160 statuspath = str;
161 status = stat.status->status;
162 fetchedstatus = status;
163 if ( askedpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
165 itemStates |= ITEMIS_FOLDER;
166 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
167 itemStates |= ITEMIS_FOLDERINGIT;
169 //if ((stat.status->entry)&&(stat.status->entry->uuid))
170 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
172 else
174 // sometimes, git_client_status() returns with an error.
175 // in that case, we have to check if the working copy is versioned
176 // anyway to show the 'correct' context menu
177 if (askedpath.HasAdminDir())
178 status = git_wc_status_normal;
182 catch ( ... )
184 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
187 // TODO: should we really assume any sub-directory to be versioned
188 // or only if it contains versioned files
189 itemStates |= askedpath.GetAdminDirMask();
191 if ((status == git_wc_status_unversioned) || (status == git_wc_status_ignored) || (status == git_wc_status_none))
192 itemStates &= ~ITEMIS_INGIT;
194 if (status == git_wc_status_ignored)
195 itemStates |= ITEMIS_IGNORED;
196 if (status == git_wc_status_normal)
197 itemStates |= ITEMIS_NORMAL;
198 if (status == git_wc_status_conflicted)
199 itemStates |= ITEMIS_CONFLICTED;
200 if (status == git_wc_status_added)
201 itemStates |= ITEMIS_ADDED;
202 if (status == git_wc_status_deleted)
203 itemStates |= ITEMIS_DELETED;
206 } // for (int i = 0; i < count; i++)
207 GlobalUnlock ( drop );
208 ReleaseStgMedium ( &stg );
210 } // if (m_State == FileStateDropHandler)
211 else
213 //Enumerate PIDLs which the user has selected
214 CIDA* cida = (CIDA*)GlobalLock(medium.hGlobal);
215 ItemIDList parent( GetPIDLFolder (cida));
217 int count = cida->cidl;
218 BOOL statfetched = FALSE;
219 for (int i = 0; i < count; ++i)
221 ItemIDList child (GetPIDLItem (cida, i), &parent);
222 std::wstring str = child.toString();
223 if ((str.empty() == false)&&(g_ShellCache.IsContextPathAllowed(str.c_str())))
225 //check if our menu is requested for a git admin directory
226 if (GitAdminDir::IsAdminDirPath(str.c_str()))
227 continue;
229 files_.push_back(str);
230 CTGitPath strpath;
231 strpath.SetFromWin(str.c_str());
232 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".diff") == 0) ? ITEMIS_PATCHFILE : 0;
233 itemStates |= (strpath.GetFileExtension().CompareNoCase(L".patch") == 0) ? ITEMIS_PATCHFILE : 0;
234 if (!statfetched)
236 //get the git status of the item
237 git_wc_status_kind status = git_wc_status_none;
238 if ((g_ShellCache.IsSimpleContext())&&(strpath.IsDirectory()))
240 if (strpath.HasAdminDir())
241 status = git_wc_status_normal;
243 else
247 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(str.c_str()))
249 CTGitPath tpath(str.c_str());
250 if(!tpath.HasAdminDir())
252 status = git_wc_status_none;
253 continue;
255 if(tpath.IsAdminDir())
257 status = git_wc_status_none;
258 continue;
260 TGITCacheResponse itemStatus;
261 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
262 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
264 fetchedstatus = status = (git_wc_status_kind)itemStatus.m_status;
265 if (strpath.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
267 itemStates |= ITEMIS_FOLDER;
268 if ((status != git_wc_status_unversioned) && (status != git_wc_status_ignored) && (status != git_wc_status_none))
269 itemStates |= ITEMIS_FOLDERINGIT;
271 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
272 itemStates |= ITEMIS_CONFLICTED;
275 else
277 GitStatus stat;
278 if (strpath.HasAdminDir())
279 stat.GetStatus(strpath, false, false, true);
280 statuspath = str;
281 if (stat.status)
283 status = stat.status->status;
284 fetchedstatus = status;
285 if ( strpath.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
287 itemStates |= ITEMIS_FOLDER;
288 if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
289 itemStates |= ITEMIS_FOLDERINGIT;
291 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
292 if (status == git_wc_status_conflicted)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
293 itemStates |= ITEMIS_CONFLICTED;
294 //if ((stat.status->entry)&&(stat.status->entry->uuid))
295 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
297 else
299 // sometimes, git_client_status() returns with an error.
300 // in that case, we have to check if the working copy is versioned
301 // anyway to show the 'correct' context menu
302 if (strpath.HasAdminDir())
304 status = git_wc_status_normal;
305 fetchedstatus = status;
309 statfetched = TRUE;
311 catch ( ... )
313 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
317 itemStates |= strpath.GetAdminDirMask();
319 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
320 itemStates &= ~ITEMIS_INGIT;
321 if (status == git_wc_status_ignored)
323 itemStates |= ITEMIS_IGNORED;
326 if (status == git_wc_status_normal)
327 itemStates |= ITEMIS_NORMAL;
328 if (status == git_wc_status_conflicted)
329 itemStates |= ITEMIS_CONFLICTED;
330 if (status == git_wc_status_added)
331 itemStates |= ITEMIS_ADDED;
332 if (status == git_wc_status_deleted)
333 itemStates |= ITEMIS_DELETED;
336 } // for (int i = 0; i < count; ++i)
337 ItemIDList child (GetPIDLItem (cida, 0), &parent);
338 if (g_ShellCache.HasGITAdminDir(child.toString().c_str(), FALSE))
339 itemStates |= ITEMIS_INVERSIONEDFOLDER;
341 if (GitAdminDir::IsBareRepo(child.toString().c_str()))
342 itemStates = ITEMIS_BAREREPO;
344 GlobalUnlock(medium.hGlobal);
346 // if the item is a versioned folder, check if there's a patch file
347 // in the clipboard to be used in "Apply Patch"
348 UINT cFormatDiff = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
349 if (cFormatDiff)
351 if (IsClipboardFormatAvailable(cFormatDiff))
352 itemStates |= ITEMIS_PATCHINCLIPBOARD;
354 if (IsClipboardFormatAvailable(CF_HDROP))
355 itemStates |= ITEMIS_PATHINCLIPBOARD;
359 ReleaseStgMedium ( &medium );
360 if (medium.pUnkForRelease)
362 IUnknown* relInterface = (IUnknown*)medium.pUnkForRelease;
363 relInterface->Release();
368 // get folder background
369 if (pIDFolder)
371 ItemIDList list(pIDFolder);
372 folder_ = list.toString();
373 git_wc_status_kind status = git_wc_status_none;
374 if (IsClipboardFormatAvailable(CF_HDROP))
375 itemStatesFolder |= ITEMIS_PATHINCLIPBOARD;
377 CTGitPath askedpath;
378 askedpath.SetFromWin(folder_.c_str());
380 if (g_ShellCache.IsContextPathAllowed(folder_.c_str()))
382 if (folder_.compare(statuspath)!=0)
384 CString worktreePath;
385 askedpath.HasAdminDir(&worktreePath);
386 uuidTarget = worktreePath;
389 if (g_ShellCache.GetCacheType() == ShellCache::exe && g_ShellCache.IsPathAllowed(folder_.c_str()))
391 CTGitPath tpath(folder_.c_str());
392 if(!tpath.HasAdminDir())
393 status = git_wc_status_none;
394 else if(tpath.IsAdminDir())
395 status = git_wc_status_none;
396 else
398 TGITCacheResponse itemStatus;
399 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
400 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
401 status = (git_wc_status_kind)itemStatus.m_status;
404 else
406 GitStatus stat;
407 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
408 if (stat.status)
409 status = stat.status->status;
410 else
412 // sometimes, git_client_status() returns with an error.
413 // in that case, we have to check if the working copy is versioned
414 // anyway to show the 'correct' context menu
415 if (askedpath.HasAdminDir())
416 status = git_wc_status_normal;
420 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
421 itemStatesFolder |= askedpath.GetAdminDirMask();
423 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
424 itemStates &= ~ITEMIS_INGIT;
426 if (status == git_wc_status_normal)
427 itemStatesFolder |= ITEMIS_NORMAL;
428 if (status == git_wc_status_conflicted)
429 itemStatesFolder |= ITEMIS_CONFLICTED;
430 if (status == git_wc_status_added)
431 itemStatesFolder |= ITEMIS_ADDED;
432 if (status == git_wc_status_deleted)
433 itemStatesFolder |= ITEMIS_DELETED;
435 if (GitAdminDir::IsBareRepo(askedpath.GetWinPath()))
436 itemStatesFolder = ITEMIS_BAREREPO;
438 catch ( ... )
440 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
443 else
445 status = fetchedstatus;
447 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
448 itemStatesFolder |= askedpath.GetAdminDirMask();
450 if (status == git_wc_status_ignored)
451 itemStatesFolder |= ITEMIS_IGNORED;
452 itemStatesFolder |= ITEMIS_FOLDER;
453 if (files_.empty())
454 itemStates |= ITEMIS_ONLYONE;
455 if (m_State != FileStateDropHandler)
456 itemStates |= itemStatesFolder;
458 else
460 folder_.clear();
461 status = fetchedstatus;
464 if (files_.size() == 2)
465 itemStates |= ITEMIS_TWO;
466 if ((files_.size() == 1)&&(g_ShellCache.IsContextPathAllowed(files_.front().c_str())))
468 itemStates |= ITEMIS_ONLYONE;
469 if (m_State != FileStateDropHandler)
471 if (PathIsDirectory(files_.front().c_str()))
473 folder_ = files_.front();
474 git_wc_status_kind status = git_wc_status_none;
475 CTGitPath askedpath;
476 askedpath.SetFromWin(folder_.c_str());
478 if (folder_.compare(statuspath)!=0)
482 GitStatus stat;
483 stat.GetStatus(CTGitPath(folder_.c_str()), false, false, true);
484 if (stat.status)
485 status = stat.status->status;
487 catch ( ... )
489 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Exception in GitStatus::GetStatus()\n");
492 else
494 status = fetchedstatus;
496 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
497 itemStates |= askedpath.GetAdminDirMask();
499 if ((status == git_wc_status_unversioned)||(status == git_wc_status_ignored)||(status == git_wc_status_none))
500 itemStates &= ~ITEMIS_INGIT;
502 if (status == git_wc_status_ignored)
503 itemStates |= ITEMIS_IGNORED;
504 itemStates |= ITEMIS_FOLDER;
505 if (status == git_wc_status_added)
506 itemStates |= ITEMIS_ADDED;
507 if (status == git_wc_status_deleted)
508 itemStates |= ITEMIS_DELETED;
515 return S_OK;
518 void CShellExt::InsertGitMenu(BOOL istop, HMENU menu, UINT pos, UINT_PTR id, UINT stringid, UINT icon, UINT idCmdFirst, GitCommands com, UINT /*uFlags*/)
520 TCHAR menutextbuffer[512] = {0};
521 TCHAR verbsbuffer[255] = {0};
522 MAKESTRING(stringid);
524 if (istop)
526 //menu entry for the top context menu, so append an "Git " before
527 //the menu text to indicate where the entry comes from
528 wcscpy_s(menutextbuffer, 255, L"Git ");
529 if (!g_ShellCache.HasShellMenuAccelerators())
531 // remove the accelerators
532 tstring temp = stringtablebuffer;
533 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
534 wcscpy_s(stringtablebuffer, 255, temp.c_str());
537 wcscat_s(menutextbuffer, 255, stringtablebuffer);
539 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
540 // so we have an easy and fast way to check the current branch
541 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
542 if (com == ShellMenuCommit)
544 // get branch name
545 CTGitPath path(folder_.empty() ? files_.front().c_str() : folder_.c_str());
546 CString sProjectRoot;
547 CString sBranchName;
549 if (path.GetAdminDirMask() & ITEMIS_SUBMODULE)
551 if (istop)
552 wcscpy_s(menutextbuffer, 255, L"Git ");
553 else
554 menutextbuffer[0] = L'\0';
555 MAKESTRING(IDS_MENUCOMMITSUBMODULE);
556 wcscat_s(menutextbuffer, 255, stringtablebuffer);
559 if (path.HasAdminDir(&sProjectRoot) && !CGit::GetCurrentBranchFromFile(sProjectRoot, sBranchName))
561 if (sBranchName.GetLength() == 2 * GIT_HASH_SIZE)
563 // if SHA1 only show 4 first bytes
564 BOOL bIsSha1 = TRUE;
565 for (int i = 0; i < 2 * GIT_HASH_SIZE; ++i)
566 if ( !iswxdigit(sBranchName[i]) )
568 bIsSha1 = FALSE;
569 break;
571 if (bIsSha1)
572 sBranchName = sBranchName.Left(8) + L"...";
575 // sanity check
576 if (sBranchName.GetLength() > 64)
577 sBranchName = sBranchName.Left(64) + L"...";
579 // scan to before "..."
580 LPTSTR s = menutextbuffer + wcslen(menutextbuffer)-1;
581 if (s > menutextbuffer)
583 while (s > menutextbuffer)
585 if (*s != L'.')
587 s++;
588 break;
590 s--;
593 else
595 s = menutextbuffer;
598 // append branch name and end with ...
599 wcscpy_s(s, 255 - wcslen(menutextbuffer) - 1, L" -> \"" + sBranchName + L"\"...");
603 if (com == ShellMenuDiffLater)
605 std::wstring sPath = regDiffLater;
606 if (!sPath.empty())
608 // add the path of the saved file
609 wchar_t compact[2 * GIT_HASH_SIZE] = { 0 };
610 PathCompactPathEx(compact, sPath.c_str(), _countof(compact) - 1, 0);
611 MAKESTRING(IDS_MENUDIFFNOW);
612 CString sMenu;
613 sMenu.Format(CString(stringtablebuffer), compact);
614 wcscpy_s(menutextbuffer, sMenu);
618 MENUITEMINFO menuiteminfo = { 0 };
619 menuiteminfo.cbSize = sizeof(menuiteminfo);
620 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
621 menuiteminfo.fType = MFT_STRING;
622 menuiteminfo.dwTypeData = menutextbuffer;
623 if (icon)
625 menuiteminfo.fMask |= MIIM_BITMAP;
626 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
628 menuiteminfo.wID = (UINT)id;
629 InsertMenuItem(menu, pos, TRUE, &menuiteminfo);
631 if (istop)
633 //menu entry for the top context menu, so append an "Git " before
634 //the menu text to indicate where the entry comes from
635 wcscpy_s(menutextbuffer, 255, L"Git ");
637 LoadString(g_hResInst, stringid, verbsbuffer, _countof(verbsbuffer));
638 wcscat_s(menutextbuffer, 255, verbsbuffer);
639 auto verb = std::wstring(menutextbuffer);
640 if (verb.find('&') != -1)
642 verb.erase(verb.find('&'),1);
644 myVerbsMap[verb] = id - idCmdFirst;
645 myVerbsMap[verb] = id;
646 myVerbsIDMap[id - idCmdFirst] = verb;
647 myVerbsIDMap[id] = verb;
648 // We store the relative and absolute diameter
649 // (drawitem callback uses absolute, others relative)
650 myIDMap[id - idCmdFirst] = com;
651 myIDMap[id] = com;
652 if (!istop)
653 mySubMenuMap[pos] = com;
656 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring& tempfile)
658 tempfile = std::wstring();
659 //write all selected files and paths to a temporary file
660 //for TortoiseGitProc.exe to read out again.
661 DWORD written = 0;
662 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
663 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
664 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
665 GetTortoiseGitTempPath(pathlength+1, path.get());
666 GetTempFileName(path.get(), L"git", 0, tempFile.get());
667 tempfile = std::wstring(tempFile.get());
669 CAutoFile file = ::CreateFile(tempFile.get(),
670 GENERIC_WRITE,
671 FILE_SHARE_READ,
672 nullptr,
673 CREATE_ALWAYS,
674 FILE_ATTRIBUTE_TEMPORARY,
675 nullptr);
677 if (!file)
678 return false;
680 if (!IsClipboardFormatAvailable(CF_HDROP))
681 return false;
682 if (!OpenClipboard(nullptr))
683 return false;
685 HGLOBAL hglb = GetClipboardData(CF_HDROP);
686 SCOPE_EXIT
688 GlobalUnlock(hglb);
689 CloseClipboard();
691 HDROP hDrop = (HDROP)GlobalLock(hglb);
692 if (!hDrop)
693 return false;
694 SCOPE_EXIT { GlobalUnlock(hDrop); };
696 TCHAR szFileName[MAX_PATH] = {0};
697 UINT cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
698 for(UINT i = 0; i < cFiles; ++i)
700 DragQueryFile(hDrop, i, szFileName, _countof(szFileName));
701 std::wstring filename = szFileName;
702 ::WriteFile (file, filename.c_str(), (DWORD)filename.size()*sizeof(TCHAR), &written, 0);
703 ::WriteFile(file, L"\n", 2, &written, 0);
706 return true;
709 std::wstring CShellExt::WriteFileListToTempFile()
711 //write all selected files and paths to a temporary file
712 //for TortoiseGitProc.exe to read out again.
713 DWORD pathlength = GetTortoiseGitTempPath(0, nullptr);
714 auto path = std::make_unique<TCHAR[]>(pathlength + 1);
715 auto tempFile = std::make_unique<TCHAR[]>(pathlength + 100);
716 GetTortoiseGitTempPath(pathlength + 1, path.get());
717 GetTempFileName(path.get(), L"git", 0, tempFile.get());
718 auto retFilePath = std::wstring(tempFile.get());
720 CAutoFile file = ::CreateFile (tempFile.get(),
721 GENERIC_WRITE,
722 FILE_SHARE_READ,
723 nullptr,
724 CREATE_ALWAYS,
725 FILE_ATTRIBUTE_TEMPORARY,
726 nullptr);
728 if (!file)
730 MessageBox(nullptr, L"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile.get()), L"TortoiseGit", MB_ICONERROR);
731 return std::wstring();
734 DWORD written = 0;
735 if (files_.empty())
737 ::WriteFile (file, folder_.c_str(), (DWORD)folder_.size()*sizeof(TCHAR), &written, 0);
738 ::WriteFile(file, L"\n", 2, &written, 0);
741 for (const auto& file_ : files_)
743 ::WriteFile(file, file_.c_str(), (DWORD)file_.size() * sizeof(TCHAR), &written, 0);
744 ::WriteFile(file, L"\n", 2, &written, 0);
746 return retFilePath;
749 STDMETHODIMP CShellExt::QueryDropContext(UINT uFlags, UINT idCmdFirst, HMENU hMenu, UINT &indexMenu)
751 if (!CRegStdDWORD(L"Software\\TortoiseGit\\EnableDragContextMenu", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY))
752 return S_OK;
754 PreserveChdir preserveChdir;
755 LoadLangDll();
757 if ((uFlags & CMF_DEFAULTONLY)!=0)
758 return S_OK; //we don't change the default action
760 if (files_.empty() || folder_.empty())
761 return S_OK;
763 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
764 return S_OK;
766 bool bSourceAndTargetFromSameRepository = ((uuidSource.size() == uuidTarget.size() && _wcsnicmp(uuidSource.c_str(), uuidTarget.c_str(), uuidSource.size()) == 0)) || uuidSource.empty() || uuidTarget.empty();
768 //the drop handler only has eight commands, but not all are visible at the same time:
769 //if the source file(s) are under version control then those files can be moved
770 //to the new location or they can be moved with a rename,
771 //if they are unversioned then they can be added to the working copy
772 //if they are versioned, they also can be exported to an unversioned location
773 UINT idCmd = idCmdFirst;
775 bool moveAvailable = false;
776 // Git move here
777 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
778 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && ((itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT))))
780 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVEMENU, 0, idCmdFirst, ShellMenuDropMove, uFlags);
781 moveAvailable = true;
784 // Git move and rename here
785 // 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
786 if ((bSourceAndTargetFromSameRepository || (itemStatesFolder & ITEMIS_ADDED)) && (itemStatesFolder & ITEMIS_FOLDERINGIT) && (itemStates & (ITEMIS_NORMAL | ITEMIS_INGIT | ITEMIS_FOLDERINGIT)) && (itemStates & ITEMIS_ONLYONE))
788 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPMOVERENAMEMENU, 0, idCmdFirst, ShellMenuDropMoveRename, uFlags);
789 moveAvailable = true;
792 // Git copy here
793 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
794 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
795 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
797 // Git copy and rename here, source and target from same repository
798 // available if source is a single, versioned but not added item, target is versioned or target folder is added
799 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
800 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
802 // Git add here
803 // available if target is versioned and source is either unversioned or from another repository
804 if ((itemStatesFolder & ITEMIS_FOLDERINGIT) && (((~itemStates) & ITEMIS_INGIT) || !bSourceAndTargetFromSameRepository) && !moveAvailable)
805 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYADDMENU, 0, idCmdFirst, ShellMenuDropCopyAdd, uFlags);
807 // Git export here
808 // available if source is versioned and a folder
809 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
810 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
812 // Git export all here
813 // available if source is versioned and a folder
814 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
815 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
817 // apply patch
818 // available if source is a patchfile
819 if (itemStates & ITEMIS_PATCHFILE)
821 if (itemStates & ITEMIS_ONLYONE)
822 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUAPPLYPATCH, 0, idCmdFirst, ShellMenuApplyPatch, uFlags);
823 InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_MENUIMPORTPATCH, 0, idCmdFirst, ShellMenuImportPatchDrop, uFlags);
826 // separator
827 if (idCmd != idCmdFirst)
828 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
830 TweakMenu(hMenu);
832 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
835 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
836 UINT indexMenu,
837 UINT idCmdFirst,
838 UINT /*idCmdLast*/,
839 UINT uFlags)
841 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Shell :: QueryContextMenu itemStates=%ld\n", itemStates);
842 PreserveChdir preserveChdir;
844 //first check if our drop handler is called
845 //and then (if true) provide the context menu for the
846 //drop handler
847 if (m_State == FileStateDropHandler)
849 return QueryDropContext(uFlags, idCmdFirst, hMenu, indexMenu);
852 if ((uFlags & CMF_DEFAULTONLY)!=0)
853 return S_OK; //we don't change the default action
855 if (files_.empty() && folder_.empty())
856 return S_OK;
858 if (((uFlags & 0x000f)!=CMF_NORMAL)&&(!(uFlags & CMF_EXPLORE))&&(!(uFlags & CMF_VERBSONLY)))
859 return S_OK;
861 int csidlarray[] =
863 CSIDL_BITBUCKET,
864 CSIDL_CDBURN_AREA,
865 CSIDL_COMMON_FAVORITES,
866 CSIDL_COMMON_STARTMENU,
867 CSIDL_COMPUTERSNEARME,
868 CSIDL_CONNECTIONS,
869 CSIDL_CONTROLS,
870 CSIDL_COOKIES,
871 CSIDL_FAVORITES,
872 CSIDL_FONTS,
873 CSIDL_HISTORY,
874 CSIDL_INTERNET,
875 CSIDL_INTERNET_CACHE,
876 CSIDL_NETHOOD,
877 CSIDL_NETWORK,
878 CSIDL_PRINTERS,
879 CSIDL_PRINTHOOD,
880 CSIDL_RECENT,
881 CSIDL_SENDTO,
882 CSIDL_STARTMENU,
885 if (IsIllegalFolder(folder_, csidlarray))
886 return S_OK;
888 if (folder_.empty())
890 // folder is empty, but maybe files are selected
891 if (files_.empty())
892 return S_OK; // nothing selected - we don't have a menu to show
893 // check whether a selected entry is an UID - those are namespace extensions
894 // which we can't handle
895 for (const auto& file : files_)
897 if (CStringUtils::StartsWith(file.c_str(), L"::{"))
898 return S_OK;
901 else
903 if (CStringUtils::StartsWith(folder_.c_str(), L"::{"))
904 return S_OK;
907 if (((uFlags & CMF_EXTENDEDVERBS) == 0) && g_ShellCache.HideMenusForUnversionedItems())
909 if ((itemStates & (ITEMIS_INGIT | ITEMIS_INVERSIONEDFOLDER | ITEMIS_FOLDERINGIT | ITEMIS_BAREREPO)) == 0)
910 return S_OK;
913 //check if our menu is requested for a git admin directory
914 if (GitAdminDir::IsAdminDirPath(folder_.c_str()))
915 return S_OK;
917 if (uFlags & CMF_EXTENDEDVERBS)
918 itemStates |= ITEMIS_EXTENDED;
920 regDiffLater.read();
921 if (!std::wstring(regDiffLater).empty())
922 itemStates |= ITEMIS_HASDIFFLATER;
924 const BOOL bShortcut = !!(uFlags & CMF_VERBSONLY);
925 if ( bShortcut && (files_.size()==1))
927 // Don't show the context menu for a link if the
928 // destination is not part of a working copy.
929 // It would only show the standard menu items
930 // which are already shown for the lnk-file.
931 CString path = files_.front().c_str();
932 if (!GitAdminDir::HasAdminDir(path))
934 return S_OK;
938 //check if we already added our menu entry for a folder.
939 //we check that by iterating through all menu entries and check if
940 //the dwItemData member points to our global ID string. That string is set
941 //by our shell extension when the folder menu is inserted.
942 TCHAR menubuf[MAX_PATH] = {0};
943 int count = GetMenuItemCount(hMenu);
944 for (int i=0; i<count; ++i)
946 MENUITEMINFO miif = { 0 };
947 miif.cbSize = sizeof(MENUITEMINFO);
948 miif.fMask = MIIM_DATA;
949 miif.dwTypeData = menubuf;
950 miif.cch = _countof(menubuf);
951 GetMenuItemInfo(hMenu, i, TRUE, &miif);
952 if (miif.dwItemData == (ULONG_PTR)g_MenuIDString)
953 return S_OK;
956 LoadLangDll();
957 UINT idCmd = idCmdFirst;
959 //create the sub menu
960 HMENU subMenu = CreateMenu();
961 int indexSubMenu = 0;
963 unsigned __int64 topmenu = g_ShellCache.GetMenuLayout();
964 unsigned __int64 menumask = g_ShellCache.GetMenuMask();
965 unsigned __int64 menuex = g_ShellCache.GetMenuExt();
967 int menuIndex = 0;
968 bool bAddSeparator = false;
969 bool bMenuEntryAdded = false;
970 bool bMenuEmpty = true;
971 // insert separator at start
972 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
973 bool bShowIcons = !!DWORD(CRegStdDWORD(L"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE, false, HKEY_CURRENT_USER, KEY_WOW64_64KEY));
975 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
977 if (menuInfo[menuIndex].command == ShellSeparator)
979 // we don't add a separator immediately. Because there might not be
980 // another 'normal' menu entry after we insert a separator.
981 // we simply set a flag here, indicating that before the next
982 // 'normal' menu entry, a separator should be added.
983 if (!bMenuEmpty)
984 bAddSeparator = true;
985 if (bMenuEntryAdded)
986 bAddSeparator = true;
988 else
990 // check the conditions whether to show the menu entry or not
991 bool bInsertMenu = ShouldInsertItem(menuInfo[menuIndex]);
992 if (menuInfo[menuIndex].menuID & menuex)
994 if( !(itemStates & ITEMIS_EXTENDED) )
995 bInsertMenu = false;
998 if (menuInfo[menuIndex].menuID & (~menumask))
1000 if (bInsertMenu)
1002 bool bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1003 // insert a separator
1004 if ((bMenuEntryAdded)&&(bAddSeparator)&&(!bIsTop))
1006 bAddSeparator = false;
1007 bMenuEntryAdded = false;
1008 InsertMenu(subMenu, indexSubMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, nullptr);
1009 idCmd++;
1012 // handle special cases (sub menus)
1013 if ((menuInfo[menuIndex].command == ShellMenuIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuUnIgnoreSub)||(menuInfo[menuIndex].command == ShellMenuDeleteIgnoreSub))
1015 if(InsertIgnoreSubmenus(idCmd, idCmdFirst, hMenu, subMenu, indexMenu, indexSubMenu, topmenu, bShowIcons, uFlags))
1016 bMenuEntryAdded = true;
1018 else
1020 bIsTop = ((topmenu & menuInfo[menuIndex].menuID) != 0);
1022 // insert the menu entry
1023 InsertGitMenu( bIsTop,
1024 bIsTop ? hMenu : subMenu,
1025 bIsTop ? indexMenu++ : indexSubMenu++,
1026 idCmd++,
1027 menuInfo[menuIndex].menuTextID,
1028 bShowIcons ? menuInfo[menuIndex].iconID : 0,
1029 idCmdFirst,
1030 menuInfo[menuIndex].command,
1031 uFlags);
1032 if (!bIsTop)
1034 bMenuEntryAdded = true;
1035 bMenuEmpty = false;
1036 bAddSeparator = false;
1042 menuIndex++;
1045 // do not show TortoiseGit menu if it's empty
1046 if (bMenuEmpty)
1048 if (idCmd - idCmdFirst > 0)
1050 //separator after
1051 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1053 TweakMenu(hMenu);
1055 //return number of menu items added
1056 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1059 //add sub menu to main context menu
1060 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1061 //see http://support.microsoft.com/default.aspx?scid=kb;en-us;214477 for details of that.
1062 MAKESTRING(IDS_MENUSUBMENU);
1063 if (!g_ShellCache.HasShellMenuAccelerators())
1065 // remove the accelerators
1066 tstring temp = stringtablebuffer;
1067 temp.erase(std::remove(temp.begin(), temp.end(), '&'), temp.end());
1068 wcscpy_s(stringtablebuffer, temp.c_str());
1070 MENUITEMINFO menuiteminfo = { 0 };
1071 menuiteminfo.cbSize = sizeof(menuiteminfo);
1072 menuiteminfo.fType = MFT_STRING;
1073 menuiteminfo.dwTypeData = stringtablebuffer;
1075 UINT uIcon = bShowIcons ? IDI_APP : 0;
1076 if (!folder_.empty())
1078 uIcon = bShowIcons ? IDI_MENUFOLDER : 0;
1079 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFolder;
1080 myIDMap[idCmd] = ShellSubMenuFolder;
1081 menuiteminfo.dwItemData = (ULONG_PTR)g_MenuIDString;
1083 else if (!bShortcut && (files_.size()==1))
1085 uIcon = bShowIcons ? IDI_MENUFILE : 0;
1086 myIDMap[idCmd - idCmdFirst] = ShellSubMenuFile;
1087 myIDMap[idCmd] = ShellSubMenuFile;
1089 else if (bShortcut && (files_.size()==1))
1091 uIcon = bShowIcons ? IDI_MENULINK : 0;
1092 myIDMap[idCmd - idCmdFirst] = ShellSubMenuLink;
1093 myIDMap[idCmd] = ShellSubMenuLink;
1095 else if (!files_.empty())
1097 uIcon = bShowIcons ? IDI_MENUMULTIPLE : 0;
1098 myIDMap[idCmd - idCmdFirst] = ShellSubMenuMultiple;
1099 myIDMap[idCmd] = ShellSubMenuMultiple;
1101 else
1103 myIDMap[idCmd - idCmdFirst] = ShellSubMenu;
1104 myIDMap[idCmd] = ShellSubMenu;
1106 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
1107 if (uIcon)
1109 menuiteminfo.fMask |= MIIM_BITMAP;
1110 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, uIcon);
1112 menuiteminfo.hSubMenu = subMenu;
1113 menuiteminfo.wID = idCmd++;
1114 InsertMenuItem(hMenu, indexMenu++, TRUE, &menuiteminfo);
1116 //separator after
1117 InsertMenu(hMenu, indexMenu++, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr); idCmd++;
1119 TweakMenu(hMenu);
1121 //return number of menu items added
1122 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(idCmd - idCmdFirst)));
1125 void CShellExt::TweakMenu(HMENU hMenu)
1127 MENUINFO MenuInfo = {};
1128 MenuInfo.cbSize = sizeof(MenuInfo);
1129 MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
1130 MenuInfo.dwStyle = MNS_CHECKORBMP;
1131 SetMenuInfo(hMenu, &MenuInfo);
1134 void CShellExt::AddPathCommand(tstring& gitCmd, LPCTSTR command, bool bFilesAllowed)
1136 gitCmd += command;
1137 gitCmd += L" /path:\"";
1138 if ((bFilesAllowed) && !files_.empty())
1139 gitCmd += files_.front();
1140 else
1141 gitCmd += folder_;
1142 gitCmd += L'"';
1145 void CShellExt::AddPathFileCommand(tstring& gitCmd, LPCTSTR command)
1147 tstring tempfile = WriteFileListToTempFile();
1148 gitCmd += command;
1149 gitCmd += L" /pathfile:\"";
1150 gitCmd += tempfile;
1151 gitCmd += L'"';
1152 gitCmd += L" /deletepathfile";
1155 void CShellExt::AddPathFileDropCommand(tstring& gitCmd, LPCTSTR command)
1157 tstring tempfile = WriteFileListToTempFile();
1158 gitCmd += command;
1159 gitCmd += L" /pathfile:\"";
1160 gitCmd += tempfile;
1161 gitCmd += L'"';
1162 gitCmd += L" /deletepathfile";
1163 gitCmd += L" /droptarget:\"";
1164 gitCmd += folder_;
1165 gitCmd += L'"';
1168 // This is called when you invoke a command on the menu:
1169 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
1171 PreserveChdir preserveChdir;
1172 HRESULT hr = E_INVALIDARG;
1173 if (!lpcmi)
1174 return hr;
1176 if (!files_.empty() || !folder_.empty())
1178 UINT_PTR idCmd = LOWORD(lpcmi->lpVerb);
1180 if (HIWORD(lpcmi->lpVerb))
1182 auto verb = std::wstring(MultibyteToWide(lpcmi->lpVerb));
1183 const auto verb_it = myVerbsMap.lower_bound(verb);
1184 if (verb_it != myVerbsMap.end() && verb_it->first == verb)
1185 idCmd = verb_it->second;
1186 else
1187 return hr;
1190 // See if we have a handler interface for this id
1191 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1192 if (id_it != myIDMap.end() && id_it->first == idCmd)
1194 tstring tortoiseProcPath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitProc.exe");
1195 tstring tortoiseMergePath(CPathUtils::GetAppDirectory(g_hmodThisDll) + L"TortoiseGitMerge.exe");
1197 //TortoiseGitProc expects a command line of the form:
1198 //"/command:<commandname> /pathfile:<path> /startrev:<startrevision> /endrev:<endrevision> /deletepathfile
1199 // or
1200 //"/command:<commandname> /path:<path> /startrev:<startrevision> /endrev:<endrevision>
1202 //* path is a path to a single file/directory for commands which only act on single items (log, checkout, ...)
1203 //* pathfile is a path to a temporary file which contains a list of file paths
1204 std::wstring gitCmd = L" /command:";
1205 std::wstring tempfile;
1206 switch (id_it->second)
1208 //#region case
1209 case ShellMenuSync:
1211 TCHAR syncSeq[12] = { 0 };
1212 swprintf_s(syncSeq, L"%d", g_syncSeq++);
1213 AddPathCommand(gitCmd, L"sync", false);
1214 gitCmd += L" /seq:";
1215 gitCmd += syncSeq;
1217 break;
1218 case ShellMenuSubSync:
1219 AddPathFileCommand(gitCmd, L"subsync");
1220 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1222 gitCmd += L" /bkpath:\"";
1223 gitCmd += folder_;
1224 gitCmd += L'"';
1226 break;
1227 case ShellMenuUpdateExt:
1228 AddPathFileCommand(gitCmd, L"subupdate");
1229 if (itemStatesFolder & ITEMIS_SUBMODULECONTAINER || (itemStates & ITEMIS_SUBMODULECONTAINER && itemStates & ITEMIS_WCROOT && itemStates & ITEMIS_ONLYONE))
1231 gitCmd += L" /bkpath:\"";
1232 gitCmd += folder_;
1233 gitCmd += L'"';
1235 break;
1236 case ShellMenuCommit:
1237 AddPathFileCommand(gitCmd, L"commit");
1238 break;
1239 case ShellMenuAdd:
1240 AddPathFileCommand(gitCmd, L"add");
1241 break;
1242 case ShellMenuIgnore:
1243 AddPathFileCommand(gitCmd, L"ignore");
1244 break;
1245 case ShellMenuIgnoreCaseSensitive:
1246 AddPathFileCommand(gitCmd, L"ignore");
1247 gitCmd += L" /onlymask";
1248 break;
1249 case ShellMenuDeleteIgnore:
1250 AddPathFileCommand(gitCmd, L"ignore");
1251 gitCmd += L" /delete";
1252 break;
1253 case ShellMenuDeleteIgnoreCaseSensitive:
1254 AddPathFileCommand(gitCmd, L"ignore");
1255 gitCmd += L" /delete /onlymask";
1256 break;
1257 case ShellMenuUnIgnore:
1258 AddPathFileCommand(gitCmd, L"unignore");
1259 break;
1260 case ShellMenuUnIgnoreCaseSensitive:
1261 AddPathFileCommand(gitCmd, L"unignore");
1262 gitCmd += L" /onlymask";
1263 break;
1264 case ShellMenuMergeAbort:
1265 AddPathCommand(gitCmd, L"merge", false);
1266 gitCmd += L" /abort";
1267 break;
1268 case ShellMenuRevert:
1269 AddPathFileCommand(gitCmd, L"revert");
1270 break;
1271 case ShellMenuCleanup:
1272 AddPathFileCommand(gitCmd, L"cleanup");
1273 break;
1274 case ShellMenuSendMail:
1275 AddPathFileCommand(gitCmd, L"sendmail");
1276 break;
1277 case ShellMenuResolve:
1278 AddPathFileCommand(gitCmd, L"resolve");
1279 break;
1280 case ShellMenuSwitch:
1281 AddPathCommand(gitCmd, L"switch", false);
1282 break;
1283 case ShellMenuExport:
1284 AddPathCommand(gitCmd, L"export", false);
1285 break;
1286 case ShellMenuAbout:
1287 gitCmd += L"about";
1288 break;
1289 case ShellMenuCreateRepos:
1290 AddPathCommand(gitCmd, L"repocreate", false);
1291 break;
1292 case ShellMenuMerge:
1293 AddPathCommand(gitCmd, L"merge", false);
1294 break;
1295 case ShellMenuCopy:
1296 AddPathCommand(gitCmd, L"copy", true);
1297 break;
1298 case ShellMenuSettings:
1299 AddPathCommand(gitCmd, L"settings", true);
1300 break;
1301 case ShellMenuHelp:
1302 gitCmd += L"help";
1303 break;
1304 case ShellMenuRename:
1305 AddPathCommand(gitCmd, L"rename", true);
1306 break;
1307 case ShellMenuRemove:
1308 AddPathFileCommand(gitCmd, L"remove");
1309 if (itemStates & ITEMIS_SUBMODULE)
1310 gitCmd += L" /submodule";
1311 break;
1312 case ShellMenuRemoveKeep:
1313 AddPathFileCommand(gitCmd, L"remove");
1314 gitCmd += L" /keep";
1315 break;
1316 case ShellMenuDiff:
1317 gitCmd += L"diff /path:\"";
1318 if (files_.size() == 1)
1319 gitCmd += files_.front();
1320 else if (files_.size() == 2)
1322 auto I = files_.cbegin();
1323 gitCmd += *I;
1324 ++I;
1325 gitCmd += L"\" /path2:\"";
1326 gitCmd += *I;
1328 else
1329 gitCmd += folder_;
1330 gitCmd += L'"';
1331 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1332 gitCmd += L" /alternative";
1333 break;
1334 case ShellMenuDiffLater:
1335 if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1337 gitCmd.clear();
1338 regDiffLater.removeValue();
1340 else if (files_.size() == 1)
1342 if (std::wstring(regDiffLater).empty())
1344 gitCmd.clear();
1345 regDiffLater = files_[0];
1347 else
1349 AddPathCommand(gitCmd, L"diff", true);
1350 gitCmd += L" /path2:\"";
1351 gitCmd += std::wstring(regDiffLater);
1352 gitCmd += L'"';
1353 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1354 gitCmd += L" /alternative";
1355 regDiffLater.removeValue();
1358 else
1359 gitCmd.clear();
1360 break;
1361 case ShellMenuPrevDiff:
1362 AddPathCommand(gitCmd, L"prevdiff", true);
1363 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1364 gitCmd += L" /alternative";
1365 break;
1366 case ShellMenuDiffTwo:
1367 AddPathCommand(gitCmd, L"diffcommits", true);
1368 break;
1369 case ShellMenuDropCopyAdd:
1370 AddPathFileDropCommand(gitCmd, L"dropcopyadd");
1371 break;
1372 case ShellMenuDropCopy:
1373 AddPathFileDropCommand(gitCmd, L"dropcopy");
1374 break;
1375 case ShellMenuDropCopyRename:
1376 AddPathFileDropCommand(gitCmd, L"dropcopy");
1377 gitCmd += L" /rename";
1378 break;
1379 case ShellMenuDropMove:
1380 AddPathFileDropCommand(gitCmd, L"dropmove");
1381 break;
1382 case ShellMenuDropMoveRename:
1383 AddPathFileDropCommand(gitCmd, L"dropmove");
1384 gitCmd += L" /rename";
1385 break;
1386 case ShellMenuDropExport:
1387 AddPathFileDropCommand(gitCmd, L"dropexport");
1388 break;
1389 case ShellMenuDropExportExtended:
1390 AddPathFileDropCommand(gitCmd, L"dropexport");
1391 gitCmd += L" /extended";
1392 break;
1393 case ShellMenuLog:
1394 case ShellMenuLogSubmoduleFolder:
1395 AddPathCommand(gitCmd, L"log", true);
1396 if (id_it->second == ShellMenuLogSubmoduleFolder)
1397 gitCmd += L" /submodule";
1398 break;
1399 case ShellMenuDaemon:
1400 AddPathCommand(gitCmd, L"daemon", true);
1401 break;
1402 case ShellMenuRevisionGraph:
1403 AddPathCommand(gitCmd, L"revisiongraph", true);
1404 break;
1405 case ShellMenuConflictEditor:
1406 AddPathCommand(gitCmd, L"conflicteditor", true);
1407 if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
1408 gitCmd += L" /alternative";
1409 break;
1410 case ShellMenuGitSVNRebase:
1411 AddPathCommand(gitCmd, L"svnrebase", false);
1412 break;
1413 case ShellMenuGitSVNDCommit:
1414 AddPathCommand(gitCmd, L"svndcommit", true);
1415 break;
1416 case ShellMenuGitSVNDFetch:
1417 AddPathCommand(gitCmd, L"svnfetch", false);
1418 break;
1419 case ShellMenuGitSVNIgnore:
1420 AddPathCommand(gitCmd, L"svnignore", false);
1421 break;
1422 case ShellMenuRebase:
1423 AddPathCommand(gitCmd, L"rebase", false);
1424 break;
1425 case ShellMenuShowChanged:
1426 if (files_.size() > 1)
1427 AddPathFileCommand(gitCmd, L"repostatus");
1428 else
1429 AddPathCommand(gitCmd, L"repostatus", true);
1430 break;
1431 case ShellMenuRepoBrowse:
1432 AddPathCommand(gitCmd, L"repobrowser", false);
1433 break;
1434 case ShellMenuRefBrowse:
1435 AddPathCommand(gitCmd, L"refbrowse", false);
1436 break;
1437 case ShellMenuRefLog:
1438 AddPathCommand(gitCmd, L"reflog", false);
1439 break;
1440 case ShellMenuStashSave:
1441 AddPathCommand(gitCmd, L"stashsave", true);
1442 break;
1443 case ShellMenuStashApply:
1444 AddPathCommand(gitCmd, L"stashapply", false);
1445 break;
1446 case ShellMenuStashPop:
1447 AddPathCommand(gitCmd, L"stashpop", false);
1448 break;
1449 case ShellMenuStashList:
1450 AddPathCommand(gitCmd, L"reflog", false);
1451 gitCmd += L" /ref:refs/stash";
1452 break;
1453 case ShellMenuBisectStart:
1454 AddPathCommand(gitCmd, L"bisect", false);
1455 gitCmd += L" /start";
1456 break;
1457 case ShellMenuBisectGood:
1458 AddPathCommand(gitCmd, L"bisect", false);
1459 gitCmd += L" /good";
1460 break;
1461 case ShellMenuBisectBad:
1462 AddPathCommand(gitCmd, L"bisect", false);
1463 gitCmd += L" /bad";
1464 break;
1465 case ShellMenuBisectSkip:
1466 AddPathCommand(gitCmd, L"bisect", false);
1467 gitCmd += L" /skip";
1468 break;
1469 case ShellMenuBisectReset:
1470 AddPathCommand(gitCmd, L"bisect", false);
1471 gitCmd += L" /reset";
1472 break;
1473 case ShellMenuSubAdd:
1474 AddPathCommand(gitCmd, L"subadd", false);
1475 break;
1476 case ShellMenuBlame:
1477 AddPathCommand(gitCmd, L"blame", true);
1478 break;
1479 case ShellMenuApplyPatch:
1480 if ((itemStates & ITEMIS_PATCHINCLIPBOARD) && ((~itemStates) & ITEMIS_PATCHFILE))
1482 // if there's a patch file in the clipboard, we save it
1483 // to a temporary file and tell TortoiseGitMerge to use that one
1484 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
1485 if (cFormat && OpenClipboard(nullptr))
1487 HGLOBAL hglb = GetClipboardData(cFormat);
1488 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
1490 DWORD len = GetTortoiseGitTempPath(0, nullptr);
1491 auto path = std::make_unique<TCHAR[]>(len + 1);
1492 auto tempF = std::make_unique<TCHAR[]>(len + 100);
1493 GetTortoiseGitTempPath(len + 1, path.get());
1494 GetTempFileName(path.get(), TEXT("git"), 0, tempF.get());
1495 std::wstring sTempFile = std::wstring(tempF.get());
1497 FILE * outFile;
1498 size_t patchlen = strlen(lpstr);
1499 _wfopen_s(&outFile, sTempFile.c_str(), L"wb");
1500 if(outFile)
1502 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
1503 if (size == patchlen)
1505 itemStates |= ITEMIS_PATCHFILE;
1506 files_.clear();
1507 files_.push_back(sTempFile);
1509 fclose(outFile);
1511 GlobalUnlock(hglb);
1512 CloseClipboard();
1515 if (itemStates & ITEMIS_PATCHFILE)
1517 gitCmd = L" /diff:\"";
1518 if (!files_.empty())
1520 gitCmd += files_.front();
1521 if (itemStatesFolder & ITEMIS_FOLDERINGIT)
1523 gitCmd += L"\" /patchpath:\"";
1524 gitCmd += folder_;
1527 else
1528 gitCmd += folder_;
1529 if (itemStates & ITEMIS_INVERSIONEDFOLDER)
1530 gitCmd += L"\" /wc";
1531 else
1532 gitCmd += L'"';
1534 else
1536 gitCmd = L" /patchpath:\"";
1537 if (!files_.empty())
1538 gitCmd += files_.front();
1539 else
1540 gitCmd += folder_;
1541 gitCmd += L'"';
1543 myIDMap.clear();
1544 myVerbsIDMap.clear();
1545 myVerbsMap.clear();
1546 RunCommand(tortoiseMergePath, gitCmd, L"TortoiseGitMerge launch failed");
1547 return S_OK;
1548 break;
1549 case ShellMenuClipPaste:
1550 if (WriteClipboardPathsToTempFile(tempfile))
1552 bool bCopy = true;
1553 UINT cPrefDropFormat = RegisterClipboardFormat(L"Preferred DropEffect");
1554 if (cPrefDropFormat)
1556 if (OpenClipboard(lpcmi->hwnd))
1558 HGLOBAL hglb = GetClipboardData(cPrefDropFormat);
1559 if (hglb)
1561 DWORD* effect = (DWORD*) GlobalLock(hglb);
1562 if (*effect == DROPEFFECT_MOVE)
1563 bCopy = false;
1564 GlobalUnlock(hglb);
1566 CloseClipboard();
1570 if (bCopy)
1571 gitCmd += L"pastecopy /pathfile:\"";
1572 else
1573 gitCmd += L"pastemove /pathfile:\"";
1574 gitCmd += tempfile;
1575 gitCmd += L'"';
1576 gitCmd += L" /deletepathfile";
1577 gitCmd += L" /droptarget:\"";
1578 gitCmd += folder_;
1579 gitCmd += L'"';
1581 else return S_OK;
1582 break;
1583 case ShellMenuClone:
1584 AddPathCommand(gitCmd, L"clone", false);
1585 break;
1586 case ShellMenuPull:
1587 AddPathCommand(gitCmd, L"pull", false);
1588 break;
1589 case ShellMenuPush:
1590 AddPathCommand(gitCmd, L"push", false);
1591 break;
1592 case ShellMenuBranch:
1593 AddPathCommand(gitCmd, L"branch", false);
1594 break;
1595 case ShellMenuTag:
1596 AddPathCommand(gitCmd, L"tag", false);
1597 break;
1598 case ShellMenuFormatPatch:
1599 AddPathCommand(gitCmd, L"formatpatch", false);
1600 break;
1601 case ShellMenuImportPatch:
1602 AddPathFileCommand(gitCmd, L"importpatch");
1603 break;
1604 case ShellMenuImportPatchDrop:
1605 AddPathFileDropCommand(gitCmd, L"importpatch");
1606 break;
1607 case ShellMenuFetch:
1608 AddPathCommand(gitCmd, L"fetch", false);
1609 break;
1611 default:
1612 break;
1613 //#endregion
1614 } // switch (id_it->second)
1615 if (!gitCmd.empty())
1617 gitCmd += L" /hwnd:";
1618 TCHAR buf[30] = { 0 };
1619 swprintf_s(buf, L"%p", (void*)lpcmi->hwnd);
1620 gitCmd += buf;
1621 myIDMap.clear();
1622 myVerbsIDMap.clear();
1623 myVerbsMap.clear();
1624 RunCommand(tortoiseProcPath, gitCmd, L"TortoiseProc launch failed");
1626 hr = S_OK;
1627 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1628 } // if (files_.empty() || folder_.empty())
1629 return hr;
1632 // This is for the status bar and things like that:
1633 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
1634 UINT uFlags,
1635 UINT FAR * /*reserved*/,
1636 LPSTR pszName,
1637 UINT cchMax)
1639 PreserveChdir preserveChdir;
1640 //do we know the id?
1641 std::map<UINT_PTR, UINT_PTR>::const_iterator id_it = myIDMap.lower_bound(idCmd);
1642 if (id_it == myIDMap.end() || id_it->first != idCmd)
1644 return E_INVALIDARG; //no, we don't
1647 LoadLangDll();
1648 HRESULT hr = E_INVALIDARG;
1650 MAKESTRING(IDS_MENUDESCDEFAULT);
1651 int menuIndex = 0;
1652 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1654 if (menuInfo[menuIndex].command == (GitCommands)id_it->second)
1656 MAKESTRING(menuInfo[menuIndex].menuDescID);
1657 break;
1659 menuIndex++;
1662 const TCHAR * desc = stringtablebuffer;
1663 switch(uFlags)
1665 case GCS_HELPTEXTA:
1667 std::string help = WideToMultibyte(desc);
1668 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1669 hr = S_OK;
1670 break;
1672 case GCS_HELPTEXTW:
1674 std::wstring help = desc;
1675 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1676 hr = S_OK;
1677 break;
1679 case GCS_VERBA:
1681 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1682 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1684 std::string help = WideToMultibyte(verb_id_it->second);
1685 lstrcpynA(pszName, help.c_str(), cchMax - 1);
1686 hr = S_OK;
1689 break;
1690 case GCS_VERBW:
1692 const auto verb_id_it = myVerbsIDMap.lower_bound(idCmd);
1693 if (verb_id_it != myVerbsIDMap.end() && verb_id_it->first == idCmd)
1695 std::wstring help = verb_id_it->second;
1696 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": verb : %ws\n", help.c_str());
1697 lstrcpynW((LPWSTR)pszName, help.c_str(), cchMax - 1);
1698 hr = S_OK;
1701 break;
1703 return hr;
1706 STDMETHODIMP CShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
1708 LRESULT res;
1709 return HandleMenuMsg2(uMsg, wParam, lParam, &res);
1712 STDMETHODIMP CShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
1714 PreserveChdir preserveChdir;
1716 LRESULT res;
1717 if (!pResult)
1718 pResult = &res;
1719 *pResult = FALSE;
1721 LoadLangDll();
1722 switch (uMsg)
1724 case WM_MEASUREITEM:
1726 MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
1727 if (!lpmis)
1728 break;
1729 lpmis->itemWidth = 16;
1730 lpmis->itemHeight = 16;
1731 *pResult = TRUE;
1733 break;
1734 case WM_DRAWITEM:
1736 LPCTSTR resource;
1737 DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
1738 if (!lpdis || lpdis->CtlType != ODT_MENU)
1739 return S_OK; //not for a menu
1740 resource = GetMenuTextFromResource((int)myIDMap[lpdis->itemID]);
1741 if (!resource)
1742 return S_OK;
1743 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1744 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1745 auto hIcon = LoadIconEx(g_hResInst, resource, iconWidth, iconHeight);
1746 if (!hIcon)
1747 return S_OK;
1748 DrawIconEx(lpdis->hDC,
1749 lpdis->rcItem.left,
1750 lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - iconHeight) / 2,
1751 hIcon, iconWidth, iconHeight,
1752 0, nullptr, DI_NORMAL);
1753 DestroyIcon(hIcon);
1754 *pResult = TRUE;
1756 break;
1757 case WM_MENUCHAR:
1759 TCHAR *szItem;
1760 if (HIWORD(wParam) != MF_POPUP)
1761 return S_OK;
1762 int nChar = LOWORD(wParam);
1763 if (_istascii((wint_t)nChar) && _istupper((wint_t)nChar))
1764 nChar = tolower(nChar);
1765 // we have the char the user pressed, now search that char in all our
1766 // menu items
1767 std::vector<UINT_PTR> accmenus;
1768 for (auto It = mySubMenuMap.cbegin(); It != mySubMenuMap.cend(); ++It)
1770 LPCTSTR resource = GetMenuTextFromResource((int)mySubMenuMap[It->first]);
1771 if (!resource)
1772 continue;
1773 szItem = stringtablebuffer;
1774 TCHAR* amp = wcschr(szItem, L'&');
1775 if (!amp)
1776 continue;
1777 amp++;
1778 int ampChar = LOWORD(*amp);
1779 if (_istascii((wint_t)ampChar) && _istupper((wint_t)ampChar))
1780 ampChar = tolower(ampChar);
1781 if (ampChar == nChar)
1783 // yep, we found a menu which has the pressed key
1784 // as an accelerator. Add that menu to the list to
1785 // process later.
1786 accmenus.push_back(It->first);
1789 if (accmenus.empty())
1791 // no menu with that accelerator key.
1792 *pResult = MAKELONG(0, MNC_IGNORE);
1793 return S_OK;
1795 if (accmenus.size() == 1)
1797 // Only one menu with that accelerator key. We're lucky!
1798 // So just execute that menu entry.
1799 *pResult = MAKELONG(accmenus[0], MNC_EXECUTE);
1800 return S_OK;
1802 if (accmenus.size() > 1)
1804 // we have more than one menu item with this accelerator key!
1805 MENUITEMINFO mif;
1806 mif.cbSize = sizeof(MENUITEMINFO);
1807 mif.fMask = MIIM_STATE;
1808 for (auto it = accmenus.cbegin(); it != accmenus.cend(); ++it)
1810 GetMenuItemInfo((HMENU)lParam, (UINT)*it, TRUE, &mif);
1811 if (mif.fState == MFS_HILITE)
1813 // this is the selected item, so select the next one
1814 ++it;
1815 if (it == accmenus.end())
1816 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1817 else
1818 *pResult = MAKELONG(*it, MNC_SELECT);
1819 return S_OK;
1822 *pResult = MAKELONG(accmenus[0], MNC_SELECT);
1825 break;
1826 default:
1827 return S_OK;
1830 return S_OK;
1833 LPCTSTR CShellExt::GetMenuTextFromResource(int id)
1835 TCHAR textbuf[255] = { 0 };
1836 LPCTSTR resource = nullptr;
1837 unsigned __int64 layout = g_ShellCache.GetMenuLayout();
1838 space = 6;
1840 int menuIndex = 0;
1841 while (menuInfo[menuIndex].command != ShellMenuLastEntry)
1843 if (menuInfo[menuIndex].command == id)
1845 MAKESTRING(menuInfo[menuIndex].menuTextID);
1846 resource = MAKEINTRESOURCE(menuInfo[menuIndex].iconID);
1847 switch (id)
1849 case ShellSubMenuMultiple:
1850 case ShellSubMenuLink:
1851 case ShellSubMenuFolder:
1852 case ShellSubMenuFile:
1853 case ShellSubMenu:
1854 space = 0;
1855 break;
1856 default:
1857 space = (layout & menuInfo[menuIndex].menuID) ? 0 : 6;
1858 if (layout & menuInfo[menuIndex].menuID)
1860 wcscpy_s(textbuf, 255, L"Git ");
1861 wcscat_s(textbuf, 255, stringtablebuffer);
1862 wcscpy_s(stringtablebuffer, 255, textbuf);
1864 break;
1866 return resource;
1868 menuIndex++;
1870 return nullptr;
1873 bool CShellExt::IsIllegalFolder(const std::wstring& folder, int* cslidarray)
1875 int i=0;
1876 TCHAR buf[MAX_PATH] = {0}; //MAX_PATH ok, since SHGetSpecialFolderPath doesn't return the required buffer length!
1877 LPITEMIDLIST pidl = nullptr;
1878 while (cslidarray[i])
1880 ++i;
1881 pidl = nullptr;
1882 if (SHGetFolderLocation(nullptr, cslidarray[i - 1], nullptr, 0, &pidl) != S_OK)
1883 continue;
1884 if (!SHGetPathFromIDList(pidl, buf))
1886 // not a file system path, definitely illegal for our use
1887 CoTaskMemFree(pidl);
1888 continue;
1890 CoTaskMemFree(pidl);
1891 if (!buf[0])
1892 continue;
1893 if (wcscmp(buf, folder.c_str()) == 0)
1894 return true;
1896 return false;
1899 bool CShellExt::InsertIgnoreSubmenus(UINT &idCmd, UINT idCmdFirst, HMENU hMenu, HMENU subMenu, UINT &indexMenu, int &indexSubMenu, unsigned __int64 topmenu, bool bShowIcons, UINT /*uFlags*/)
1901 HMENU ignoresubmenu = nullptr;
1902 int indexignoresub = 0;
1903 bool bShowIgnoreMenu = false;
1904 TCHAR maskbuf[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1905 TCHAR ignorepath[MAX_PATH] = {0}; // MAX_PATH is ok, since this only holds a filename
1906 if (files_.empty())
1907 return false;
1908 UINT icon = bShowIcons ? IDI_IGNORE : 0;
1910 auto I = files_.cbegin();
1911 if (wcsrchr(I->c_str(), L'\\'))
1912 wcscpy_s(ignorepath, wcsrchr(I->c_str(), L'\\') + 1);
1913 else
1914 wcscpy_s(ignorepath, I->c_str());
1915 if ((itemStates & ITEMIS_IGNORED) && (!ignoredprops.empty()))
1917 // check if the item name is ignored or the mask
1918 size_t p = 0;
1919 const size_t pathLength = wcslen(ignorepath);
1920 while ( (p=ignoredprops.find( ignorepath,p )) != -1 )
1922 if ( (p==0 || ignoredprops[p-1]==TCHAR('\n'))
1923 && (p + pathLength == ignoredprops.length() || ignoredprops[p + pathLength + 1] == TCHAR('\n')))
1925 break;
1927 p++;
1929 if (p!=-1)
1931 ignoresubmenu = CreateMenu();
1932 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1933 auto verb = std::wstring(ignorepath);
1934 myVerbsMap[verb] = idCmd - idCmdFirst;
1935 myVerbsMap[verb] = idCmd;
1936 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1937 myVerbsIDMap[idCmd] = verb;
1938 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnore;
1939 myIDMap[idCmd++] = ShellMenuUnIgnore;
1940 bShowIgnoreMenu = true;
1942 wcscpy_s(maskbuf, L"*");
1943 if (wcsrchr(ignorepath, L'.'))
1945 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
1946 p = ignoredprops.find(maskbuf);
1947 if ((p!=-1) &&
1948 ((ignoredprops.compare(maskbuf) == 0) || (ignoredprops.find(L'\n', p) == p + wcslen(maskbuf) + 1) || (ignoredprops.rfind(L'\n', p) == p - 1)))
1950 if (!ignoresubmenu)
1951 ignoresubmenu = CreateMenu();
1953 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
1954 auto verb = std::wstring(maskbuf);
1955 myVerbsMap[verb] = idCmd - idCmdFirst;
1956 myVerbsMap[verb] = idCmd;
1957 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1958 myVerbsIDMap[idCmd] = verb;
1959 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreCaseSensitive;
1960 myIDMap[idCmd++] = ShellMenuUnIgnoreCaseSensitive;
1961 bShowIgnoreMenu = true;
1965 else if ((itemStates & ITEMIS_IGNORED) == 0)
1967 bShowIgnoreMenu = true;
1968 ignoresubmenu = CreateMenu();
1969 if (itemStates & ITEMIS_ONLYONE)
1971 if (itemStates & ITEMIS_INGIT)
1973 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1974 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
1975 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
1977 wcscpy_s(maskbuf, L"*");
1978 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
1980 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
1981 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
1982 auto verb = std::wstring(maskbuf);
1983 myVerbsMap[verb] = idCmd - idCmdFirst;
1984 myVerbsMap[verb] = idCmd;
1985 myVerbsIDMap[idCmd - idCmdFirst] = verb;
1986 myVerbsIDMap[idCmd] = verb;
1987 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
1988 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
1991 else
1993 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
1994 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
1995 myIDMap[idCmd++] = ShellMenuIgnore;
1997 wcscpy_s(maskbuf, L"*");
1998 if (!(itemStates & ITEMIS_FOLDER) && wcsrchr(ignorepath, L'.'))
2000 wcscat_s(maskbuf, wcsrchr(ignorepath, L'.'));
2001 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, maskbuf);
2002 auto verb = std::wstring(maskbuf);
2003 myVerbsMap[verb] = idCmd - idCmdFirst;
2004 myVerbsMap[verb] = idCmd;
2005 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2006 myVerbsIDMap[idCmd] = verb;
2007 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2008 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2012 else
2014 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2015 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2016 // has selected more than 16 files, we won't know about that here.
2017 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2018 if (itemStates & ITEMIS_INGIT)
2020 if (files_.size() >= 16)
2022 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2);
2023 wcscpy_s(ignorepath, stringtablebuffer);
2025 else
2027 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE);
2028 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2030 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2031 auto verb = std::wstring(ignorepath);
2032 myVerbsMap[verb] = idCmd - idCmdFirst;
2033 myVerbsMap[verb] = idCmd;
2034 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2035 myVerbsIDMap[idCmd] = verb;
2036 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnore;
2037 myIDMap[idCmd++] = ShellMenuDeleteIgnore;
2039 if (files_.size() >= 16)
2041 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2);
2042 wcscpy_s(ignorepath, stringtablebuffer);
2044 else
2046 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK);
2047 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2049 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2050 verb = std::wstring(ignorepath);
2051 myVerbsMap[verb] = idCmd - idCmdFirst;
2052 myVerbsMap[verb] = idCmd;
2053 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2054 myVerbsIDMap[idCmd] = verb;
2055 myIDMap[idCmd - idCmdFirst] = ShellMenuDeleteIgnoreCaseSensitive;
2056 myIDMap[idCmd++] = ShellMenuDeleteIgnoreCaseSensitive;
2058 else
2060 if (files_.size() >= 16)
2062 MAKESTRING(IDS_MENUIGNOREMULTIPLE2);
2063 wcscpy_s(ignorepath, stringtablebuffer);
2065 else
2067 MAKESTRING(IDS_MENUIGNOREMULTIPLE);
2068 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2070 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2071 auto verb = std::wstring(ignorepath);
2072 myVerbsMap[verb] = idCmd - idCmdFirst;
2073 myVerbsMap[verb] = idCmd;
2074 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2075 myVerbsIDMap[idCmd] = verb;
2076 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnore;
2077 myIDMap[idCmd++] = ShellMenuIgnore;
2079 if (files_.size() >= 16)
2081 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2);
2082 wcscpy_s(ignorepath, stringtablebuffer);
2084 else
2086 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK);
2087 swprintf_s(ignorepath, stringtablebuffer, files_.size());
2089 InsertMenu(ignoresubmenu, indexignoresub++, MF_BYPOSITION | MF_STRING , idCmd, ignorepath);
2090 verb = std::wstring(ignorepath);
2091 myVerbsMap[verb] = idCmd - idCmdFirst;
2092 myVerbsMap[verb] = idCmd;
2093 myVerbsIDMap[idCmd - idCmdFirst] = verb;
2094 myVerbsIDMap[idCmd] = verb;
2095 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreCaseSensitive;
2096 myIDMap[idCmd++] = ShellMenuIgnoreCaseSensitive;
2101 if (bShowIgnoreMenu)
2103 MENUITEMINFO menuiteminfo = { 0 };
2104 menuiteminfo.cbSize = sizeof(menuiteminfo);
2105 menuiteminfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU | MIIM_DATA | MIIM_STRING;
2106 if (icon)
2108 menuiteminfo.fMask |= MIIM_BITMAP;
2109 menuiteminfo.hbmpItem = m_iconBitmapUtils.IconToBitmapPARGB32(g_hResInst, icon);
2111 menuiteminfo.fType = MFT_STRING;
2112 menuiteminfo.hSubMenu = ignoresubmenu;
2113 menuiteminfo.wID = idCmd;
2114 SecureZeroMemory(stringtablebuffer, sizeof(stringtablebuffer));
2115 if (itemStates & ITEMIS_IGNORED)
2116 GetMenuTextFromResource(ShellMenuUnIgnoreSub);
2117 else if (itemStates & ITEMIS_INGIT)
2118 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub);
2119 else
2120 GetMenuTextFromResource(ShellMenuIgnoreSub);
2121 menuiteminfo.dwTypeData = stringtablebuffer;
2122 menuiteminfo.cch = (UINT)min(wcslen(menuiteminfo.dwTypeData), UINT_MAX);
2124 InsertMenuItem((topmenu & MENUIGNORE) ? hMenu : subMenu, (topmenu & MENUIGNORE) ? indexMenu++ : indexSubMenu++, TRUE, &menuiteminfo);
2125 if (itemStates & ITEMIS_IGNORED)
2127 myIDMap[idCmd - idCmdFirst] = ShellMenuUnIgnoreSub;
2128 myIDMap[idCmd++] = ShellMenuUnIgnoreSub;
2130 else
2132 myIDMap[idCmd - idCmdFirst] = ShellMenuIgnoreSub;
2133 myIDMap[idCmd++] = ShellMenuIgnoreSub;
2136 return bShowIgnoreMenu;
2139 void CShellExt::RunCommand(const tstring& path, const tstring& command, LPCTSTR errorMessage)
2141 if (CCreateProcessHelper::CreateProcessDetached(path.c_str(), command.c_str()))
2143 // process started - exit
2144 return;
2147 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage, MB_OK | MB_ICONERROR);
2150 bool CShellExt::ShouldInsertItem(const MenuInfo& item) const
2152 return ShouldEnableMenu(item.first) || ShouldEnableMenu(item.second) ||
2153 ShouldEnableMenu(item.third) || ShouldEnableMenu(item.fourth);
2156 bool CShellExt::ShouldEnableMenu(const YesNoPair& pair) const
2158 if (pair.yes && pair.no)
2160 if (((pair.yes & itemStates) == pair.yes) && ((pair.no & (~itemStates)) == pair.no))
2161 return true;
2163 else if ((pair.yes) && ((pair.yes & itemStates) == pair.yes))
2164 return true;
2165 else if ((pair.no) && ((pair.no & (~itemStates)) == pair.no))
2166 return true;
2167 return false;