1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2014-2016, 2018 - TortoiseSVN
4 // Copyright (C) 2008-2023 - 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.
22 #include <winrt/base.h>
23 #include <wrl/client.h>
26 #include "ItemIDList.h"
27 #include "PreserveChdir.h"
28 #include "UnicodeUtils.h"
30 #include "GitStatus.h"
32 #include "PathUtils.h"
33 #include "CreateProcessHelper.h"
34 #include "FormatMessageWrapper.h"
35 #include "../TGitCache/CacheInterface.h"
37 #include "LoadIconEx.h"
38 #include "ClipboardHelper.h"
40 #pragma comment(lib, "comsupp.lib")
42 #define GetPIDLFolder(pida) reinterpret_cast<LPCITEMIDLIST>(reinterpret_cast<LPBYTE>(pida) + (pida)->aoffset[0])
43 #define GetPIDLItem(pida, i) reinterpret_cast<LPCITEMIDLIST>(reinterpret_cast<LPBYTE>(pida) + (pida)->aoffset[i + 1])
45 int g_shellidlist
=RegisterClipboardFormat(CFSTR_SHELLIDLIST
);
47 extern MenuInfo menuInfo
[];
48 static int g_syncSeq
= 0;
50 STDMETHODIMP
CShellExt::Initialize(LPCITEMIDLIST pIDFolder
, LPDATAOBJECT pDataObj
, HKEY
/* hRegKey */)
52 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize\n");
53 PreserveChdir preserveChdir
;
60 std::wstring statuspath
;
61 git_wc_status_kind fetchedstatus
= git_wc_status_none
;
62 // get selected files/folders
66 FORMATETC fmte
= { static_cast<CLIPFORMAT
>(g_shellidlist
),
71 HRESULT hres
= pDataObj
->GetData(&fmte
, &medium
);
73 if (SUCCEEDED(hres
) && medium
.hGlobal
)
75 if (m_State
== FileStateDropHandler
)
77 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
79 ReleaseStgMedium(&medium
);
83 FORMATETC etc
= { CF_HDROP
, nullptr, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
};
84 STGMEDIUM stg
= { TYMED_HGLOBAL
};
85 if ( FAILED( pDataObj
->GetData ( &etc
, &stg
)))
87 ReleaseStgMedium ( &medium
);
92 auto drop
= static_cast<HDROP
>(GlobalLock(stg
.hGlobal
));
95 ReleaseStgMedium ( &stg
);
96 ReleaseStgMedium ( &medium
);
100 const int count
= DragQueryFile(drop
, UINT(-1), nullptr, 0);
102 itemStates
|= ITEMIS_ONLYONE
;
103 for (int i
= 0; i
< count
; i
++)
105 // find the path length in chars
106 const UINT len
= DragQueryFile(drop
, i
, nullptr, 0);
107 if (len
== 0 || len
>= INT_MAX
)
109 auto szFileName
= std::make_unique
<wchar_t[]>(len
+ 1);
110 if (0 == DragQueryFile(drop
, i
, szFileName
.get(), len
+ 1))
112 auto str
= std::wstring(szFileName
.get());
113 if ((!str
.empty()) && (g_ShellCache
.IsContextPathAllowed(szFileName
.get())))
117 strpath
.SetFromWin(str
.c_str());
118 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
119 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
121 files_
.push_back(str
);
124 //get the git status of the item
125 git_wc_status_kind status
= git_wc_status_none
;
127 askedpath
.SetFromWin(str
.c_str());
128 CString workTreePath
;
129 if (!askedpath
.HasAdminDir(&workTreePath
) && GitAdminDir::IsBareRepo(str
.c_str()))
130 itemStates
|= ITEMIS_BAREREPO
; // TODO: optimize
131 uuidSource
= workTreePath
;
134 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
136 CTGitPath
tpath(str
.c_str());
137 if (!tpath
.HasAdminDir())
139 status
= git_wc_status_none
;
142 if (tpath
.IsAdminDir())
144 status
= git_wc_status_none
;
147 TGITCacheResponse itemStatus
= { 0 };
148 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
150 fetchedstatus
= status
= static_cast<git_wc_status_kind
>(itemStatus
.m_status
);
151 if (askedpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
153 itemStates
|= ITEMIS_FOLDER
;
154 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
155 itemStates
|= ITEMIS_FOLDERINGIT
;
162 stat
.GetStatus(CTGitPath(str
.c_str()), false, false, true);
166 status
= stat
.status
->status
;
167 fetchedstatus
= status
;
168 if ( askedpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
170 itemStates
|= ITEMIS_FOLDER
;
171 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
172 itemStates
|= ITEMIS_FOLDERINGIT
;
174 //if ((stat.status->entry)&&(stat.status->entry->uuid))
175 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
179 // sometimes, git_client_status() returns with an error.
180 // in that case, we have to check if the working copy is versioned
181 // anyway to show the 'correct' context menu
182 if (askedpath
.HasAdminDir())
183 status
= git_wc_status_normal
;
189 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
192 // TODO: should we really assume any sub-directory to be versioned
193 // or only if it contains versioned files
194 itemStates
|= askedpath
.GetAdminDirMask();
196 if ((status
== git_wc_status_unversioned
) || (status
== git_wc_status_ignored
) || (status
== git_wc_status_none
))
197 itemStates
&= ~ITEMIS_INGIT
;
199 if (status
== git_wc_status_ignored
)
200 itemStates
|= ITEMIS_IGNORED
;
201 if (status
== git_wc_status_normal
)
202 itemStates
|= ITEMIS_NORMAL
;
203 if (status
== git_wc_status_conflicted
)
204 itemStates
|= ITEMIS_CONFLICTED
;
205 if (status
== git_wc_status_added
)
206 itemStates
|= ITEMIS_ADDED
;
207 if (status
== git_wc_status_deleted
)
208 itemStates
|= ITEMIS_DELETED
;
211 } // for (int i = 0; i < count; i++)
212 GlobalUnlock ( drop
);
213 ReleaseStgMedium ( &stg
);
215 } // if (m_State == FileStateDropHandler)
218 //Enumerate PIDLs which the user has selected
219 auto cida
= static_cast<CIDA
*>(GlobalLock(medium
.hGlobal
));
220 ItemIDList
parent( GetPIDLFolder (cida
));
222 const int count
= cida
->cidl
;
223 BOOL statfetched
= FALSE
;
224 for (int i
= 0; i
< count
; ++i
)
226 ItemIDList
child (GetPIDLItem (cida
, i
), &parent
);
227 std::wstring str
= child
.toString();
228 if ((str
.empty() == false)&&(g_ShellCache
.IsContextPathAllowed(str
.c_str())))
230 //check if our menu is requested for a git admin directory
231 if (GitAdminDir::IsAdminDirPath(str
.c_str()))
234 files_
.push_back(str
);
236 strpath
.SetFromWin(str
.c_str());
237 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".diff") == 0) ? ITEMIS_PATCHFILE
: 0;
238 itemStates
|= (strpath
.GetFileExtension().CompareNoCase(L
".patch") == 0) ? ITEMIS_PATCHFILE
: 0;
241 //get the git status of the item
242 git_wc_status_kind status
= git_wc_status_none
;
243 if ((g_ShellCache
.IsSimpleContext())&&(strpath
.IsDirectory()))
245 if (strpath
.HasAdminDir())
246 status
= git_wc_status_normal
;
252 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(str
.c_str()))
254 CTGitPath
tpath(str
.c_str());
255 if(!tpath
.HasAdminDir())
257 status
= git_wc_status_none
;
260 if(tpath
.IsAdminDir())
262 status
= git_wc_status_none
;
265 TGITCacheResponse itemStatus
= { 0 };
266 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
268 fetchedstatus
= status
= static_cast<git_wc_status_kind
>(itemStatus
.m_status
);
269 if (strpath
.IsDirectory())//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
271 itemStates
|= ITEMIS_FOLDER
;
272 if ((status
!= git_wc_status_unversioned
) && (status
!= git_wc_status_ignored
) && (status
!= git_wc_status_none
))
273 itemStates
|= ITEMIS_FOLDERINGIT
;
275 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
276 itemStates
|= ITEMIS_CONFLICTED
;
282 if (strpath
.HasAdminDir())
283 stat
.GetStatus(strpath
, false, false, true);
287 status
= stat
.status
->status
;
288 fetchedstatus
= status
;
289 if ( strpath
.IsDirectory() )//if ((stat.status->entry)&&(stat.status->entry->kind == git_node_dir))
291 itemStates
|= ITEMIS_FOLDER
;
292 if ((status
!= git_wc_status_unversioned
)&&(status
!= git_wc_status_ignored
)&&(status
!= git_wc_status_none
))
293 itemStates
|= ITEMIS_FOLDERINGIT
;
295 // TODO: do we need to check that it's not a dir? does conflict options makes sense for dir in git?
296 if (status
== git_wc_status_conflicted
)//if ((stat.status->entry)&&(stat.status->entry->conflict_wrk))
297 itemStates
|= ITEMIS_CONFLICTED
;
298 //if ((stat.status->entry)&&(stat.status->entry->uuid))
299 // uuidSource = CUnicodeUtils::StdGetUnicode(stat.status->entry->uuid);
303 // sometimes, git_client_status() returns with an error.
304 // in that case, we have to check if the working copy is versioned
305 // anyway to show the 'correct' context menu
306 if (strpath
.HasAdminDir())
308 status
= git_wc_status_normal
;
309 fetchedstatus
= status
;
317 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
321 itemStates
|= strpath
.GetAdminDirMask();
323 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
324 itemStates
&= ~ITEMIS_INGIT
;
325 if (status
== git_wc_status_ignored
)
327 itemStates
|= ITEMIS_IGNORED
;
330 if (status
== git_wc_status_normal
)
331 itemStates
|= ITEMIS_NORMAL
;
332 if (status
== git_wc_status_conflicted
)
333 itemStates
|= ITEMIS_CONFLICTED
;
334 if (status
== git_wc_status_added
)
335 itemStates
|= ITEMIS_ADDED
;
336 if (status
== git_wc_status_deleted
)
337 itemStates
|= ITEMIS_DELETED
;
340 } // for (int i = 0; i < count; ++i)
341 ItemIDList
child (GetPIDLItem (cida
, 0), &parent
);
342 if (g_ShellCache
.HasGITAdminDir(child
.toString().c_str(), FALSE
))
343 itemStates
|= ITEMIS_INVERSIONEDFOLDER
;
345 if (GitAdminDir::IsBareRepo(child
.toString().c_str()))
346 itemStates
= ITEMIS_BAREREPO
;
348 GlobalUnlock(medium
.hGlobal
);
350 // if the item is a versioned folder, check if there's a patch file
351 // in the clipboard to be used in "Apply Patch"
352 const UINT cFormatDiff
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
355 if (IsClipboardFormatAvailable(cFormatDiff
))
356 itemStates
|= ITEMIS_PATCHINCLIPBOARD
;
358 if (IsClipboardFormatAvailable(CF_HDROP
))
359 itemStates
|= ITEMIS_PATHINCLIPBOARD
;
363 ReleaseStgMedium ( &medium
);
364 if (medium
.pUnkForRelease
)
366 IUnknown
* relInterface
= medium
.pUnkForRelease
;
367 relInterface
->Release();
372 // get folder background
375 ItemIDList
list(pIDFolder
);
376 folder_
= list
.toString();
377 git_wc_status_kind status
= git_wc_status_none
;
378 if (IsClipboardFormatAvailable(CF_HDROP
))
379 itemStatesFolder
|= ITEMIS_PATHINCLIPBOARD
;
382 askedpath
.SetFromWin(folder_
.c_str());
384 if (g_ShellCache
.IsContextPathAllowed(folder_
.c_str()))
386 if (folder_
.compare(statuspath
)!=0)
388 CString worktreePath
;
389 askedpath
.HasAdminDir(&worktreePath
);
390 uuidTarget
= worktreePath
;
393 if (g_ShellCache
.GetCacheType() == ShellCache::exe
&& g_ShellCache
.IsPathAllowed(folder_
.c_str()))
395 CTGitPath
tpath(folder_
.c_str());
396 if(!tpath
.HasAdminDir())
397 status
= git_wc_status_none
;
398 else if(tpath
.IsAdminDir())
399 status
= git_wc_status_none
;
402 TGITCacheResponse itemStatus
= { 0 };
403 if (m_remoteCacheLink
.GetStatusFromRemoteCache(tpath
, &itemStatus
, true))
404 status
= static_cast<git_wc_status_kind
>(itemStatus
.m_status
);
410 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
412 status
= stat
.status
->status
;
415 // sometimes, git_client_status() returns with an error.
416 // in that case, we have to check if the working copy is versioned
417 // anyway to show the 'correct' context menu
418 if (askedpath
.HasAdminDir())
419 status
= git_wc_status_normal
;
423 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
424 itemStatesFolder
|= askedpath
.GetAdminDirMask();
426 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
427 itemStates
&= ~ITEMIS_INGIT
;
429 if (status
== git_wc_status_normal
)
430 itemStatesFolder
|= ITEMIS_NORMAL
;
431 if (status
== git_wc_status_conflicted
)
432 itemStatesFolder
|= ITEMIS_CONFLICTED
;
433 if (status
== git_wc_status_added
)
434 itemStatesFolder
|= ITEMIS_ADDED
;
435 if (status
== git_wc_status_deleted
)
436 itemStatesFolder
|= ITEMIS_DELETED
;
438 if (GitAdminDir::IsBareRepo(askedpath
.GetWinPath()))
439 itemStatesFolder
= ITEMIS_BAREREPO
;
443 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
448 status
= fetchedstatus
;
450 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
451 itemStatesFolder
|= askedpath
.GetAdminDirMask();
453 if (status
== git_wc_status_ignored
)
454 itemStatesFolder
|= ITEMIS_IGNORED
;
455 itemStatesFolder
|= ITEMIS_FOLDER
;
457 itemStates
|= ITEMIS_ONLYONE
;
458 if (m_State
!= FileStateDropHandler
)
459 itemStates
|= itemStatesFolder
;
464 status
= fetchedstatus
;
467 if (files_
.size() == 2)
468 itemStates
|= ITEMIS_TWO
;
469 if ((files_
.size() == 1)&&(g_ShellCache
.IsContextPathAllowed(files_
.front().c_str())))
471 itemStates
|= ITEMIS_ONLYONE
;
472 if (m_State
!= FileStateDropHandler
)
474 if (PathIsDirectory(files_
.front().c_str()))
476 folder_
= files_
.front();
477 git_wc_status_kind status
= git_wc_status_none
;
479 askedpath
.SetFromWin(folder_
.c_str());
481 if (folder_
.compare(statuspath
)!=0)
486 stat
.GetStatus(CTGitPath(folder_
.c_str()), false, false, true);
488 status
= stat
.status
->status
;
492 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Exception in GitStatus::GetStatus()\n");
497 status
= fetchedstatus
;
499 //if ((status != git_wc_status_unversioned)&&(status != git_wc_status_ignored)&&(status != git_wc_status_none))
500 itemStates
|= askedpath
.GetAdminDirMask();
502 if ((status
== git_wc_status_unversioned
)||(status
== git_wc_status_ignored
)||(status
== git_wc_status_none
))
503 itemStates
&= ~ITEMIS_INGIT
;
505 if (status
== git_wc_status_ignored
)
506 itemStates
|= ITEMIS_IGNORED
;
507 itemStates
|= ITEMIS_FOLDER
;
508 if (status
== git_wc_status_added
)
509 itemStates
|= ITEMIS_ADDED
;
510 if (status
== git_wc_status_deleted
)
511 itemStates
|= ITEMIS_DELETED
;
521 void CShellExt::InsertGitMenu(BOOL istop
, HMENU menu
, UINT pos
, UINT_PTR id
, UINT stringid
, UINT icon
, UINT idCmdFirst
, GitCommands com
, UINT
/*uFlags*/)
523 wchar_t menutextbuffer
[512] = { 0 };
524 wchar_t verbsbuffer
[255] = { 0 };
525 MAKESTRING(stringid
);
529 //menu entry for the top context menu, so append an "Git " before
530 //the menu text to indicate where the entry comes from
531 wcscpy_s(menutextbuffer
, L
"Git ");
532 if (!g_ShellCache
.HasShellMenuAccelerators())
534 // remove the accelerators
535 std::wstring temp
= stringtablebuffer
;
536 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
537 wcscpy_s(stringtablebuffer
, temp
.c_str());
540 wcscat_s(menutextbuffer
, stringtablebuffer
);
542 // insert branch name into "Git Commit..." entry, so it looks like "Git Commit "master"..."
543 // so we have an easy and fast way to check the current branch
544 // (the other alternative is using a separate disabled menu entry, the code is already done but commented out)
545 if (com
== ShellMenuCommit
)
548 CTGitPath
path(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
549 CString sProjectRoot
;
552 if (path
.GetAdminDirMask() & ITEMIS_SUBMODULE
)
555 wcscpy_s(menutextbuffer
, L
"Git ");
557 menutextbuffer
[0] = L
'\0';
558 MAKESTRING(IDS_MENUCOMMITSUBMODULE
);
559 wcscat_s(menutextbuffer
, stringtablebuffer
);
562 if (path
.HasAdminDir(&sProjectRoot
) && !CGit::GetCurrentBranchFromFile(sProjectRoot
, sBranchName
))
564 if (sBranchName
.GetLength() == 2 * GIT_HASH_SIZE
)
566 // if SHA1 only show 4 first bytes
568 for (int i
= 0; i
< 2 * GIT_HASH_SIZE
; ++i
)
569 if ( !iswxdigit(sBranchName
[i
]) )
575 sBranchName
= sBranchName
.Left(g_Git
.GetShortHASHLength()) + L
"...";
579 if (sBranchName
.GetLength() > 64)
580 sBranchName
= sBranchName
.Left(64) + L
"...";
582 // scan to before "..."
583 LPWSTR s
= menutextbuffer
+ wcslen(menutextbuffer
)-1;
584 if (s
> menutextbuffer
)
586 while (s
> menutextbuffer
)
601 // append branch name and end with ...
602 wcscpy_s(s
, 255 - wcslen(menutextbuffer
) - 1, L
" -> \"" + CStringUtils::EscapeAccellerators(sBranchName
) + L
"\"...");
606 if (com
== ShellMenuDiffLater
)
608 std::wstring sPath
= regDiffLater
;
611 // add the path of the saved file
612 wchar_t compact
[2 * GIT_HASH_SIZE
] = { 0 };
613 PathCompactPathEx(compact
, sPath
.c_str(), _countof(compact
) - 1, 0);
614 MAKESTRING(IDS_MENUDIFFNOW
);
616 sMenu
.Format(CString(stringtablebuffer
), compact
);
617 wcscpy_s(menutextbuffer
, sMenu
);
621 MENUITEMINFO menuiteminfo
= { 0 };
622 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
623 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_STRING
;
624 menuiteminfo
.fType
= MFT_STRING
;
625 menuiteminfo
.dwTypeData
= menutextbuffer
;
628 menuiteminfo
.fMask
|= MIIM_BITMAP
;
629 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
631 menuiteminfo
.wID
= static_cast<UINT
>(id
);
633 InsertMenuItem(menu
, pos
, TRUE
, &menuiteminfo
);
635 m_explorerCommands
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(menutextbuffer
, icon
, com
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
638 //menu entry for the top context menu, so append an "Git " before
639 //the menu text to indicate where the entry comes from
640 wcscpy_s(menutextbuffer
, L
"Git ");
642 LoadString(g_hResInst
, stringid
, verbsbuffer
, _countof(verbsbuffer
));
643 wcscat_s(menutextbuffer
, verbsbuffer
);
644 auto verb
= std::wstring(menutextbuffer
);
645 if (verb
.find('&') != -1)
647 verb
.erase(verb
.find('&'),1);
649 myVerbsMap
[verb
] = id
- idCmdFirst
;
650 myVerbsMap
[verb
] = id
;
651 myVerbsIDMap
[id
- idCmdFirst
] = verb
;
652 myVerbsIDMap
[id
] = verb
;
653 // We store the relative and absolute diameter
654 // (drawitem callback uses absolute, others relative)
655 myIDMap
[id
- idCmdFirst
] = com
;
658 mySubMenuMap
[pos
] = com
;
661 bool CShellExt::WriteClipboardPathsToTempFile(std::wstring
& tempfile
)
663 tempfile
= std::wstring();
664 //write all selected files and paths to a temporary file
665 //for TortoiseGitProc.exe to read out again.
667 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
668 auto path
= std::make_unique
<wchar_t[]>(pathlength
+ 1);
669 auto tempFile
= std::make_unique
<wchar_t[]>(pathlength
+ 100);
670 GetTortoiseGitTempPath(pathlength
+1, path
.get());
671 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
672 tempfile
= std::wstring(tempFile
.get());
674 CAutoFile file
= ::CreateFile(tempFile
.get(),
679 FILE_ATTRIBUTE_TEMPORARY
,
685 if (!IsClipboardFormatAvailable(CF_HDROP
))
687 CClipboardHelper clipboardHelper
;
688 if (!clipboardHelper
.Open(nullptr))
691 HGLOBAL hglb
= GetClipboardData(CF_HDROP
);
696 auto hDrop
= static_cast<HDROP
>(GlobalLock(hglb
));
699 SCOPE_EXIT
{ GlobalUnlock(hDrop
); };
701 wchar_t szFileName
[MAX_PATH
] = { 0 };
702 const UINT cFiles
= DragQueryFile(hDrop
, 0xFFFFFFFF, nullptr, 0);
703 for(UINT i
= 0; i
< cFiles
; ++i
)
705 DragQueryFile(hDrop
, i
, szFileName
, _countof(szFileName
));
706 std::wstring filename
= szFileName
;
707 ::WriteFile (file
, filename
.c_str(), static_cast<DWORD
>(filename
.size()) * sizeof(wchar_t), &written
, nullptr);
708 ::WriteFile(file
, L
"\n", 2, &written
, nullptr);
714 std::wstring
CShellExt::WriteFileListToTempFile(bool bFoldersOnly
, const std::vector
<std::wstring
>& files
, const std::wstring folder
)
716 //write all selected files and paths to a temporary file
717 //for TortoiseGitProc.exe to read out again.
718 DWORD pathlength
= GetTortoiseGitTempPath(0, nullptr);
719 auto path
= std::make_unique
<wchar_t[]>(pathlength
+ 1);
720 auto tempFile
= std::make_unique
<wchar_t[]>(pathlength
+ 100);
721 GetTortoiseGitTempPath(pathlength
+ 1, path
.get());
722 GetTempFileName(path
.get(), L
"git", 0, tempFile
.get());
723 auto retFilePath
= std::wstring(tempFile
.get());
725 CAutoFile file
= ::CreateFile (tempFile
.get(),
730 FILE_ATTRIBUTE_TEMPORARY
,
735 MessageBox(nullptr, L
"Could not create temporary file. Please check (the permissions of) your temp-folder: " + CString(tempFile
.get()), L
"TortoiseGit", MB_ICONERROR
);
736 return std::wstring();
742 ::WriteFile (file
, folder
.c_str(), static_cast<DWORD
>(folder
.size()) * sizeof(wchar_t), &written
, 0);
743 ::WriteFile(file
, L
"\n", 2, &written
, 0);
746 for (const auto& file_
: files
)
748 if (bFoldersOnly
&& !PathIsDirectory(file_
.c_str()))
751 ::WriteFile(file
, file_
.c_str(), static_cast<DWORD
>(file_
.size()) * sizeof(wchar_t), &written
, 0);
752 ::WriteFile(file
, L
"\n", 2, &written
, 0);
757 STDMETHODIMP
CShellExt::QueryDropContext(UINT uFlags
, UINT idCmdFirst
, HMENU hMenu
, UINT
&indexMenu
)
759 if (!CRegStdDWORD(L
"Software\\TortoiseGit\\EnableDragContextMenu", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
))
762 PreserveChdir preserveChdir
;
765 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
766 return S_OK
; //we don't change the default action
768 if (files_
.empty() || folder_
.empty())
771 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
774 bool bSourceAndTargetFromSameRepository
= ((uuidSource
.size() == uuidTarget
.size() && _wcsnicmp(uuidSource
.c_str(), uuidTarget
.c_str(), uuidSource
.size()) == 0)) || uuidSource
.empty() || uuidTarget
.empty();
776 //the drop handler only has eight commands, but not all are visible at the same time:
777 //if the source file(s) are under version control then those files can be moved
778 //to the new location or they can be moved with a rename,
779 //if they are unversioned then they can be added to the working copy
780 //if they are versioned, they also can be exported to an unversioned location
781 UINT idCmd
= idCmdFirst
;
783 bool moveAvailable
= false;
785 // available if source is versioned but not added, 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
))))
788 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVEMENU
, 0, idCmdFirst
, ShellMenuDropMove
, uFlags
);
789 moveAvailable
= true;
792 // Git move and rename here
793 // 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
794 if ((bSourceAndTargetFromSameRepository
|| (itemStatesFolder
& ITEMIS_ADDED
)) && (itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (itemStates
& (ITEMIS_NORMAL
| ITEMIS_INGIT
| ITEMIS_FOLDERINGIT
)) && (itemStates
& ITEMIS_ONLYONE
))
796 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPMOVERENAMEMENU
, 0, idCmdFirst
, ShellMenuDropMoveRename
, uFlags
);
797 moveAvailable
= true;
801 // available if source is versioned but not added, target is versioned, source and target from same repository or target folder is added
802 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&((~itemStates) & ITEMIS_ADDED))
803 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYMENU, 0, idCmdFirst, ShellMenuDropCopy, uFlags);
805 // Git copy and rename here, source and target from same repository
806 // available if source is a single, versioned but not added item, target is versioned or target folder is added
807 //if ((bSourceAndTargetFromSameRepository||(itemStatesFolder & ITEMIS_ADDED))&&(itemStatesFolder & ITEMIS_FOLDERINGIT)&&(itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_ONLYONE)&&((~itemStates) & ITEMIS_ADDED))
808 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPCOPYRENAMEMENU, 0, idCmdFirst, ShellMenuDropCopyRename, uFlags);
811 // available if target is versioned and source is either unversioned or from another repository
812 if ((itemStatesFolder
& ITEMIS_FOLDERINGIT
) && (((~itemStates
) & ITEMIS_INGIT
) || !bSourceAndTargetFromSameRepository
) && !moveAvailable
)
813 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPCOPYADDMENU
, 0, idCmdFirst
, ShellMenuDropCopyAdd
, uFlags
);
816 // available if source is versioned and a folder
817 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
818 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTMENU, 0, idCmdFirst, ShellMenuDropExport, uFlags);
820 // Git export all here
821 // available if source is versioned and a folder
822 //if ((itemStates & ITEMIS_INGIT)&&(itemStates & ITEMIS_FOLDER))
823 // InsertGitMenu(FALSE, hMenu, indexMenu++, idCmd++, IDS_DROPEXPORTEXTENDEDMENU, 0, idCmdFirst, ShellMenuDropExportExtended, uFlags);
826 // available if source is a patchfile
827 if (itemStates
& ITEMIS_PATCHFILE
)
829 if (itemStates
& ITEMIS_ONLYONE
)
830 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUAPPLYPATCH
, 0, idCmdFirst
, ShellMenuApplyPatch
, uFlags
);
831 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_MENUIMPORTPATCH
, 0, idCmdFirst
, ShellMenuImportPatchDrop
, uFlags
);
834 if ((itemStates
& ITEMIS_ONLYONE
) && (itemStates
& (ITEMIS_WCROOT
| ITEMIS_BAREREPO
)) && !(itemStatesFolder
& ITEMIS_INVERSIONEDFOLDER
))
835 InsertGitMenu(FALSE
, hMenu
, indexMenu
++, idCmd
++, IDS_DROPNEWWORKTREE
, 0, idCmdFirst
, ShellMenuDropNewWorktree
, uFlags
);
838 if (idCmd
!= idCmdFirst
)
839 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
843 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, static_cast<USHORT
>(idCmd
- idCmdFirst
)));
846 STDMETHODIMP
CShellExt::QueryContextMenu(HMENU hMenu
, UINT indexMenu
, UINT idCmdFirst
, UINT
/*idCmdLast*/, UINT uFlags
)
848 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: QueryContextMenu itemStates=%ld\n", itemStates
);
849 PreserveChdir preserveChdir
;
851 //first check if our drop handler is called
852 //and then (if true) provide the context menu for the
854 if (m_State
== FileStateDropHandler
)
856 return QueryDropContext(uFlags
, idCmdFirst
, hMenu
, indexMenu
);
859 if ((uFlags
& CMF_DEFAULTONLY
)!=0)
860 return S_OK
; //we don't change the default action
862 if (files_
.empty() && folder_
.empty())
865 if (((uFlags
& 0x000f)!=CMF_NORMAL
)&&(!(uFlags
& CMF_EXPLORE
))&&(!(uFlags
& CMF_VERBSONLY
)))
868 if (IsIllegalFolder(folder_
))
873 // folder is empty, but maybe files are selected
875 return S_OK
; // nothing selected - we don't have a menu to show
876 // check whether a selected entry is an UID - those are namespace extensions
877 // which we can't handle
878 if (std::any_of(files_
.cbegin(), files_
.cend(), [](auto& file
) { return CStringUtils::StartsWith(file
.c_str(), L
"::{"); }))
883 if (CStringUtils::StartsWith(folder_
.c_str(), L
"::{"))
887 if (((uFlags
& CMF_EXTENDEDVERBS
) == 0) && g_ShellCache
.HideMenusForUnversionedItems())
889 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
| ITEMIS_BAREREPO
)) == 0)
893 //check if our menu is requested for a git admin directory
894 if (GitAdminDir::IsAdminDirPath(folder_
.c_str()))
897 if (uFlags
& CMF_EXTENDEDVERBS
)
898 itemStates
|= ITEMIS_EXTENDED
;
901 if (!std::wstring(regDiffLater
).empty())
902 itemStates
|= ITEMIS_HASDIFFLATER
;
904 const BOOL bShortcut
= !!(uFlags
& CMF_VERBSONLY
);
905 if ( bShortcut
&& (files_
.size()==1))
907 // Don't show the context menu for a link if the
908 // destination is not part of a working copy.
909 // It would only show the standard menu items
910 // which are already shown for the lnk-file.
911 CString path
= files_
.front().c_str();
912 if (!GitAdminDir::HasAdminDir(path
))
920 //check if we already added our menu entry for a folder.
921 //we check that by iterating through all menu entries and check if
922 //the dwItemData member points to our global ID string. That string is set
923 //by our shell extension when the folder menu is inserted.
924 wchar_t menubuf
[MAX_PATH
] = { 0 };
925 const int count
= GetMenuItemCount(hMenu
);
926 for (int i
=0; i
<count
; ++i
)
928 MENUITEMINFO miif
= { 0 };
929 miif
.cbSize
= sizeof(MENUITEMINFO
);
930 miif
.fMask
= MIIM_DATA
;
931 miif
.dwTypeData
= menubuf
;
932 miif
.cch
= _countof(menubuf
);
933 GetMenuItemInfo(hMenu
, i
, TRUE
, &miif
);
934 if (miif
.dwItemData
== reinterpret_cast<ULONG_PTR
>(g_MenuIDString
))
940 UINT idCmd
= idCmdFirst
;
942 //create the sub menu
943 HMENU subMenu
= hMenu
? CreateMenu() : nullptr;
944 int indexSubMenu
= 0;
946 unsigned __int64 topMenu11
= g_ShellCache
.GetMenuLayout11();
947 unsigned __int64 topmenu
= g_ShellCache
.GetMenuLayout();
948 unsigned __int64 menumask
= g_ShellCache
.GetMenuMask();
949 unsigned __int64 menuex
= g_ShellCache
.GetMenuExt();
952 bool bAddSeparator
= false;
953 bool bMenuEntryAdded
= false;
954 bool bMenuEmpty
= true;
957 // insert separator at start
958 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
960 bool bShowIcons
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\ShowContextMenuIcons", TRUE
, false, HKEY_CURRENT_USER
, KEY_WOW64_64KEY
));
962 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
964 if (menuInfo
[menuIndex
].command
== ShellSeparator
)
966 // we don't add a separator immediately. Because there might not be
967 // another 'normal' menu entry after we insert a separator.
968 // we simply set a flag here, indicating that before the next
969 // 'normal' menu entry, a separator should be added.
971 bAddSeparator
= true;
973 bAddSeparator
= true;
977 // check the conditions whether to show the menu entry or not
978 bool bInsertMenu
= ShouldInsertItem(menuInfo
[menuIndex
]);
979 if (menuInfo
[menuIndex
].menuID
& menuex
)
981 if( !(itemStates
& ITEMIS_EXTENDED
) )
985 if (menuInfo
[menuIndex
].menuID
& (~menumask
))
989 bool bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
990 // insert a separator
991 if ((bMenuEntryAdded
)&&(bAddSeparator
)&&(!bIsTop
))
993 bAddSeparator
= false;
994 bMenuEntryAdded
= false;
996 InsertMenu(subMenu
, indexSubMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr);
998 m_explorerCommands
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(L
"", 0, 0, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
1002 const bool isMenu11
= ((topMenu11
& menuInfo
[menuIndex
].menuID
) != 0);
1003 // handle special cases (sub menus)
1004 if ((menuInfo
[menuIndex
].command
== ShellMenuIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuUnIgnoreSub
)||(menuInfo
[menuIndex
].command
== ShellMenuDeleteIgnoreSub
))
1006 if (hMenu
|| isMenu11
)
1008 if (InsertIgnoreSubmenus(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1009 bMenuEntryAdded
= true;
1012 else if (menuInfo
[menuIndex
].command
== ShellMenuLFSMenu
)
1014 if (InsertLFSSubmenu(idCmd
, idCmdFirst
, hMenu
, subMenu
, indexMenu
, indexSubMenu
, topmenu
, bShowIcons
, uFlags
))
1015 bMenuEntryAdded
= true;
1019 bIsTop
= ((topmenu
& menuInfo
[menuIndex
].menuID
) != 0);
1021 if (hMenu
|| isMenu11
)
1023 // insert the menu entry
1024 InsertGitMenu(bIsTop
,
1025 bIsTop
? hMenu
: subMenu
,
1026 bIsTop
? indexMenu
++ : indexSubMenu
++,
1028 menuInfo
[menuIndex
].menuTextID
,
1029 bShowIcons
? menuInfo
[menuIndex
].iconID
: 0,
1031 menuInfo
[menuIndex
].command
,
1035 bMenuEntryAdded
= true;
1037 bAddSeparator
= false;
1047 // do not show TortoiseGit menu if it's empty
1050 if (idCmd
- idCmdFirst
> 0)
1055 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1060 //return number of menu items added
1061 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, static_cast<USHORT
>(idCmd
- idCmdFirst
)));
1064 //add sub menu to main context menu
1065 //don't use InsertMenu because this will lead to multiple menu entries in the explorer file menu.
1066 //see https://web.archive.org/web/20090728090357/http://support.microsoft.com/kb/214477 for details of that.
1067 MAKESTRING(IDS_MENUSUBMENU
);
1068 if (!g_ShellCache
.HasShellMenuAccelerators())
1070 // remove the accelerators
1071 std::wstring temp
= stringtablebuffer
;
1072 temp
.erase(std::remove(temp
.begin(), temp
.end(), '&'), temp
.end());
1073 wcscpy_s(stringtablebuffer
, temp
.c_str());
1075 MENUITEMINFO menuiteminfo
= { 0 };
1076 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1077 menuiteminfo
.fType
= MFT_STRING
;
1078 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1080 UINT uIcon
= bShowIcons
? IDI_APP
: 0;
1081 if (!folder_
.empty())
1083 uIcon
= bShowIcons
? IDI_MENUFOLDER
: 0;
1084 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFolder
;
1085 myIDMap
[idCmd
] = ShellSubMenuFolder
;
1086 menuiteminfo
.dwItemData
= reinterpret_cast<ULONG_PTR
>(g_MenuIDString
);
1088 else if (!bShortcut
&& (files_
.size()==1))
1090 uIcon
= bShowIcons
? IDI_MENUFILE
: 0;
1091 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuFile
;
1092 myIDMap
[idCmd
] = ShellSubMenuFile
;
1094 else if (bShortcut
&& (files_
.size()==1))
1096 uIcon
= bShowIcons
? IDI_MENULINK
: 0;
1097 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuLink
;
1098 myIDMap
[idCmd
] = ShellSubMenuLink
;
1100 else if (!files_
.empty())
1102 uIcon
= bShowIcons
? IDI_MENUMULTIPLE
: 0;
1103 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenuMultiple
;
1104 myIDMap
[idCmd
] = ShellSubMenuMultiple
;
1108 myIDMap
[idCmd
- idCmdFirst
] = ShellSubMenu
;
1109 myIDMap
[idCmd
] = ShellSubMenu
;
1111 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
1114 menuiteminfo
.fMask
|= MIIM_BITMAP
;
1115 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, uIcon
);
1117 menuiteminfo
.hSubMenu
= subMenu
;
1118 menuiteminfo
.wID
= idCmd
++;
1119 InsertMenuItem(hMenu
, indexMenu
++, TRUE
, &menuiteminfo
);
1122 InsertMenu(hMenu
, indexMenu
++, MF_SEPARATOR
| MF_BYPOSITION
, 0, nullptr); idCmd
++;
1126 //return number of menu items added
1127 return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS
, 0, static_cast<USHORT
>(idCmd
- idCmdFirst
)));
1130 void CShellExt::TweakMenu(HMENU hMenu
)
1132 MENUINFO MenuInfo
= {};
1133 MenuInfo
.cbSize
= sizeof(MenuInfo
);
1134 MenuInfo
.fMask
= MIM_STYLE
| MIM_APPLYTOSUBMENUS
;
1135 MenuInfo
.dwStyle
= MNS_CHECKORBMP
;
1136 SetMenuInfo(hMenu
, &MenuInfo
);
1139 void CShellExt::AddPathCommand(std::wstring
& gitCmd
, LPCWSTR command
, bool bFilesAllowed
, const std::vector
<std::wstring
>& files
, const std::wstring folder
)
1142 gitCmd
+= L
" /path:\"";
1143 if ((bFilesAllowed
) && !files
.empty())
1144 gitCmd
+= files
.front();
1150 void CShellExt::AddPathFileCommand(std::wstring
& gitCmd
, LPCWSTR command
, const std::vector
<std::wstring
>& files
, const std::wstring folder
, bool bFoldersOnly
= false)
1152 std::wstring tempfile
= WriteFileListToTempFile(bFoldersOnly
, files
, folder
);
1154 gitCmd
+= L
" /pathfile:\"";
1157 gitCmd
+= L
" /deletepathfile";
1160 void CShellExt::AddPathFileDropCommand(std::wstring
& gitCmd
, LPCWSTR command
, const std::vector
<std::wstring
>& files
, const std::wstring folder
)
1162 std::wstring tempfile
= WriteFileListToTempFile(false, files
, folder
);
1164 gitCmd
+= L
" /pathfile:\"";
1167 gitCmd
+= L
" /deletepathfile";
1168 gitCmd
+= L
" /droptarget:\"";
1173 // This is called when you invoke a command on the menu:
1174 STDMETHODIMP
CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi
)
1176 PreserveChdir preserveChdir
;
1177 HRESULT hr
= E_INVALIDARG
;
1181 if (!files_
.empty() || !folder_
.empty())
1183 UINT_PTR idCmd
= LOWORD(lpcmi
->lpVerb
);
1185 if (HIWORD(lpcmi
->lpVerb
))
1187 auto verb
= MultibyteToWide(lpcmi
->lpVerb
);
1188 const auto verb_it
= myVerbsMap
.lower_bound(verb
);
1189 if (verb_it
!= myVerbsMap
.end() && verb_it
->first
== verb
)
1190 idCmd
= verb_it
->second
;
1195 // See if we have a handler interface for this id
1196 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1197 if (id_it
!= myIDMap
.end() && id_it
->first
== idCmd
)
1199 InvokeCommand(static_cast<int>(id_it
->second
),
1200 static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)),
1210 myVerbsIDMap
.clear();
1214 } // if (id_it != myIDMap.end() && id_it->first == idCmd)
1215 } // if (files_.empty() || folder_.empty())
1219 void CShellExt::InvokeCommand(int cmd
, const std::wstring
& appDir
, const std::wstring uuidSource
, HWND hParent
, DWORD itemStates
, DWORD itemStatesFolder
, const std::vector
<std::wstring
>& paths
, const std::wstring
& folder
, CRegStdString
& regDiffLater
, Microsoft::WRL::ComPtr
<IUnknown
> site
)
1221 // TortoiseGitProc expects a command line of the form:
1222 //"/command:<commandname> /pathfile:<path> [/deletepathfile] ...
1224 //"/command:<commandname> /path:<path> ...
1226 //* path is a path to a single file/directory for commands which only act on single items (log, sync, ...)
1227 //* pathfile is a path to a temporary file which contains a list of file paths
1228 CTraceToOutputDebugString::Instance()(__FUNCTION__
);
1229 std::wstring gitCmd
= L
" /command:";
1234 wchar_t syncSeq
[12] = { 0 };
1235 swprintf_s(syncSeq
, L
"%d", g_syncSeq
++);
1236 AddPathCommand(gitCmd
, L
"sync", false, paths
, folder
);
1237 gitCmd
+= L
" /seq:";
1241 case ShellMenuSubSync
:
1242 AddPathFileCommand(gitCmd
, L
"subsync", paths
, folder
);
1243 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1245 gitCmd
+= L
" /bkpath:\"";
1250 case ShellMenuUpdateExt
:
1251 AddPathFileCommand(gitCmd
, L
"subupdate", paths
, folder
);
1252 if (itemStatesFolder
& ITEMIS_SUBMODULECONTAINER
|| (itemStates
& ITEMIS_SUBMODULECONTAINER
&& itemStates
& ITEMIS_WCROOT
&& itemStates
& ITEMIS_ONLYONE
))
1254 gitCmd
+= L
" /bkpath:\"";
1259 case ShellMenuCommit
:
1260 AddPathFileCommand(gitCmd
, L
"commit", paths
, folder
);
1263 AddPathFileCommand(gitCmd
, L
"add", paths
, folder
);
1265 case ShellMenuIgnore
:
1266 AddPathFileCommand(gitCmd
, L
"ignore", paths
, folder
);
1268 case ShellMenuIgnoreCaseSensitive
:
1269 AddPathFileCommand(gitCmd
, L
"ignore", paths
, folder
);
1270 gitCmd
+= L
" /onlymask";
1272 case ShellMenuDeleteIgnore
:
1273 AddPathFileCommand(gitCmd
, L
"ignore", paths
, folder
);
1274 gitCmd
+= L
" /delete";
1276 case ShellMenuDeleteIgnoreCaseSensitive
:
1277 AddPathFileCommand(gitCmd
, L
"ignore", paths
, folder
);
1278 gitCmd
+= L
" /delete /onlymask";
1280 case ShellMenuUnIgnore
:
1281 AddPathFileCommand(gitCmd
, L
"unignore", paths
, folder
);
1283 case ShellMenuUnIgnoreCaseSensitive
:
1284 AddPathFileCommand(gitCmd
, L
"unignore", paths
, folder
);
1285 gitCmd
+= L
" /onlymask";
1287 case ShellMenuMergeAbort
:
1288 AddPathCommand(gitCmd
, L
"merge", true, paths
, folder
);
1289 gitCmd
+= L
" /abort";
1291 case ShellMenuRevert
:
1292 AddPathFileCommand(gitCmd
, L
"revert", paths
, folder
);
1294 case ShellMenuCleanup
:
1295 AddPathFileCommand(gitCmd
, L
"cleanup", paths
, folder
);
1297 case ShellMenuSendMail
:
1298 AddPathFileCommand(gitCmd
, L
"sendmail", paths
, folder
);
1300 case ShellMenuResolve
:
1301 AddPathFileCommand(gitCmd
, L
"resolve", paths
, folder
);
1303 case ShellMenuSwitch
:
1304 AddPathCommand(gitCmd
, L
"switch", false, paths
, folder
);
1306 case ShellMenuExport
:
1307 AddPathCommand(gitCmd
, L
"export", false, paths
, folder
);
1309 case ShellMenuAbout
:
1312 case ShellMenuCreateRepos
:
1313 AddPathCommand(gitCmd
, L
"repocreate", false, paths
, folder
);
1315 case ShellMenuMerge
:
1316 AddPathCommand(gitCmd
, L
"merge", false, paths
, folder
);
1319 AddPathCommand(gitCmd
, L
"copy", true, paths
, folder
);
1321 case ShellMenuSettings
:
1322 AddPathCommand(gitCmd
, L
"settings", true, paths
, folder
);
1327 case ShellMenuRename
:
1328 AddPathCommand(gitCmd
, L
"rename", true, paths
, folder
);
1329 if (itemStates
& ITEMIS_SUBMODULE
)
1330 gitCmd
+= L
" /submodule";
1332 case ShellMenuRemove
:
1333 AddPathFileCommand(gitCmd
, L
"remove", paths
, folder
);
1334 if (itemStates
& ITEMIS_SUBMODULE
)
1335 gitCmd
+= L
" /submodule";
1337 case ShellMenuRemoveKeep
:
1338 AddPathFileCommand(gitCmd
, L
"remove", paths
, folder
);
1339 gitCmd
+= L
" /keep";
1342 gitCmd
+= L
"diff /path:\"";
1343 if (paths
.size() == 1)
1344 gitCmd
+= paths
.front();
1345 else if (paths
.size() == 2)
1347 auto I
= paths
.cbegin();
1350 gitCmd
+= L
"\" /path2:\"";
1356 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1357 gitCmd
+= L
" /alternative";
1359 case ShellMenuDiffLater
:
1360 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
1363 regDiffLater
.removeValue();
1365 else if (paths
.size() == 1)
1367 if (std::wstring(regDiffLater
).empty())
1370 regDiffLater
= paths
[0];
1374 AddPathCommand(gitCmd
, L
"diff", true, paths
, folder
);
1375 gitCmd
+= L
" /path2:\"";
1376 gitCmd
+= std::wstring(regDiffLater
);
1378 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1379 gitCmd
+= L
" /alternative";
1380 regDiffLater
.removeValue();
1386 case ShellMenuPrevDiff
:
1387 AddPathCommand(gitCmd
, L
"prevdiff", true, paths
, folder
);
1388 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1389 gitCmd
+= L
" /alternative";
1391 case ShellMenuDiffTwo
:
1392 AddPathCommand(gitCmd
, L
"diffcommits", true, paths
, folder
);
1394 case ShellMenuDropCopyAdd
:
1395 AddPathFileDropCommand(gitCmd
, L
"dropcopyadd", paths
, folder
);
1397 case ShellMenuDropCopy
:
1398 AddPathFileDropCommand(gitCmd
, L
"dropcopy", paths
, folder
);
1400 case ShellMenuDropCopyRename
:
1401 AddPathFileDropCommand(gitCmd
, L
"dropcopy", paths
, folder
);
1402 gitCmd
+= L
" /rename";
1404 case ShellMenuDropMove
:
1405 AddPathFileDropCommand(gitCmd
, L
"dropmove", paths
, folder
);
1407 case ShellMenuDropMoveRename
:
1408 AddPathFileDropCommand(gitCmd
, L
"dropmove", paths
, folder
);
1409 gitCmd
+= L
" /rename";
1411 case ShellMenuDropExport
:
1412 AddPathFileDropCommand(gitCmd
, L
"dropexport", paths
, folder
);
1414 case ShellMenuDropExportExtended
:
1415 AddPathFileDropCommand(gitCmd
, L
"dropexport", paths
, folder
);
1416 gitCmd
+= L
" /extended";
1419 case ShellMenuLogSubmoduleFolder
:
1420 AddPathCommand(gitCmd
, L
"log", true, paths
, folder
);
1421 if (cmd
== ShellMenuLogSubmoduleFolder
)
1422 gitCmd
+= L
" /submodule";
1424 case ShellMenuDaemon
:
1425 AddPathCommand(gitCmd
, L
"daemon", true, paths
, folder
);
1427 case ShellMenuRevisionGraph
:
1428 AddPathCommand(gitCmd
, L
"revisiongraph", true, paths
, folder
);
1430 case ShellMenuConflictEditor
:
1431 AddPathCommand(gitCmd
, L
"conflicteditor", true, paths
, folder
);
1432 if (GetAsyncKeyState(VK_SHIFT
) & 0x8000)
1433 gitCmd
+= L
" /alternative";
1435 case ShellMenuGitSVNRebase
:
1436 AddPathCommand(gitCmd
, L
"svnrebase", false, paths
, folder
);
1438 case ShellMenuGitSVNDCommit
:
1439 AddPathCommand(gitCmd
, L
"svndcommit", true, paths
, folder
);
1441 case ShellMenuGitSVNDFetch
:
1442 AddPathCommand(gitCmd
, L
"svnfetch", false, paths
, folder
);
1444 case ShellMenuGitSVNIgnore
:
1445 AddPathCommand(gitCmd
, L
"svnignore", false, paths
, folder
);
1447 case ShellMenuRebase
:
1448 AddPathCommand(gitCmd
, L
"rebase", false, paths
, folder
);
1450 case ShellMenuShowChanged
:
1451 if (paths
.size() > 1)
1452 AddPathFileCommand(gitCmd
, L
"repostatus", paths
, folder
);
1454 AddPathCommand(gitCmd
, L
"repostatus", true, paths
, folder
);
1456 case ShellMenuRepoBrowse
:
1457 AddPathCommand(gitCmd
, L
"repobrowser", false, paths
, folder
);
1459 case ShellMenuRefBrowse
:
1460 AddPathCommand(gitCmd
, L
"refbrowse", false, paths
, folder
);
1462 case ShellMenuRefLog
:
1463 AddPathCommand(gitCmd
, L
"reflog", false, paths
, folder
);
1465 case ShellMenuStashSave
:
1466 AddPathCommand(gitCmd
, L
"stashsave", true, paths
, folder
);
1468 case ShellMenuStashApply
:
1469 AddPathCommand(gitCmd
, L
"stashapply", false, paths
, folder
);
1471 case ShellMenuStashPop
:
1472 AddPathCommand(gitCmd
, L
"stashpop", false, paths
, folder
);
1474 case ShellMenuStashList
:
1475 AddPathCommand(gitCmd
, L
"reflog", false, paths
, folder
);
1476 gitCmd
+= L
" /ref:refs/stash";
1478 case ShellMenuBisectStart
:
1479 AddPathCommand(gitCmd
, L
"bisect", false, paths
, folder
);
1480 gitCmd
+= L
" /start";
1482 case ShellMenuBisectGood
:
1483 AddPathCommand(gitCmd
, L
"bisect", false, paths
, folder
);
1484 gitCmd
+= L
" /good";
1486 case ShellMenuBisectBad
:
1487 AddPathCommand(gitCmd
, L
"bisect", false, paths
, folder
);
1490 case ShellMenuBisectSkip
:
1491 AddPathCommand(gitCmd
, L
"bisect", false, paths
, folder
);
1492 gitCmd
+= L
" /skip";
1494 case ShellMenuBisectReset
:
1495 AddPathCommand(gitCmd
, L
"bisect", false, paths
, folder
);
1496 gitCmd
+= L
" /reset";
1498 case ShellMenuSubAdd
:
1499 AddPathCommand(gitCmd
, L
"subadd", false, paths
, folder
);
1501 case ShellMenuBlame
:
1502 AddPathCommand(gitCmd
, L
"blame", true, paths
, folder
);
1504 case ShellMenuApplyPatch
:
1506 auto localPaths
= paths
;
1507 if ((itemStates
& ITEMIS_PATCHINCLIPBOARD
) && ((~itemStates
) & ITEMIS_PATCHFILE
))
1509 // if there's a patch file in the clipboard, we save it
1510 // to a temporary file and tell TortoiseGitMerge to use that one
1511 const UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
1512 CClipboardHelper clipboardHelper
;
1513 if (cFormat
&& clipboardHelper
.Open(nullptr))
1515 HGLOBAL hglb
= GetClipboardData(cFormat
);
1516 auto lpstr
= static_cast<LPCSTR
>(GlobalLock(hglb
));
1518 DWORD len
= GetTortoiseGitTempPath(0, nullptr);
1519 auto path
= std::make_unique
<wchar_t[]>(len
+ 1);
1520 auto tempF
= std::make_unique
<wchar_t[]>(len
+ 100);
1521 GetTortoiseGitTempPath(len
+ 1, path
.get());
1522 GetTempFileName(path
.get(), TEXT("git"), 0, tempF
.get());
1523 std::wstring sTempFile
= std::wstring(tempF
.get());
1526 size_t patchlen
= strlen(lpstr
);
1527 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
1530 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
1531 if (size
== patchlen
)
1533 itemStates
|= ITEMIS_PATCHFILE
;
1535 localPaths
.push_back(sTempFile
);
1542 if (itemStates
& ITEMIS_PATCHFILE
)
1544 gitCmd
= L
" /diff:\"";
1545 if (!localPaths
.empty())
1547 gitCmd
+= localPaths
.front();
1548 if (itemStatesFolder
& ITEMIS_FOLDERINGIT
)
1550 gitCmd
+= L
"\" /patchpath:\"";
1556 if (itemStates
& ITEMIS_INVERSIONEDFOLDER
)
1557 gitCmd
+= L
"\" /wc";
1563 gitCmd
= L
" /patchpath:\"";
1564 if (!localPaths
.empty())
1565 gitCmd
+= localPaths
.front();
1570 RunCommand(appDir
+ L
"TortoiseGitMerge.exe", gitCmd
, L
"TortoiseGitMerge launch failed", site
);
1573 case ShellMenuClipPaste
:
1575 std::wstring tempfile
;
1576 if (WriteClipboardPathsToTempFile(tempfile
))
1579 const UINT cPrefDropFormat
= RegisterClipboardFormat(L
"Preferred DropEffect");
1580 if (cPrefDropFormat
)
1582 CClipboardHelper clipboardHelper
;
1583 if (clipboardHelper
.Open(hParent
))
1585 HGLOBAL hglb
= GetClipboardData(cPrefDropFormat
);
1588 auto effect
= static_cast<DWORD
*>(GlobalLock(hglb
));
1589 if (*effect
== DROPEFFECT_MOVE
)
1597 gitCmd
+= L
"pastecopy /pathfile:\"";
1599 gitCmd
+= L
"pastemove /pathfile:\"";
1602 gitCmd
+= L
" /deletepathfile";
1603 gitCmd
+= L
" /droptarget:\"";
1611 case ShellMenuClone
:
1612 AddPathCommand(gitCmd
, L
"clone", false, paths
, folder
);
1615 AddPathFileCommand(gitCmd
, L
"pull", paths
, folder
, true);
1618 AddPathFileCommand(gitCmd
, L
"push", paths
, folder
, true);
1620 case ShellMenuBranch
:
1621 AddPathCommand(gitCmd
, L
"branch", false, paths
, folder
);
1624 AddPathCommand(gitCmd
, L
"tag", false, paths
, folder
);
1626 case ShellMenuFormatPatch
:
1627 AddPathCommand(gitCmd
, L
"formatpatch", false, paths
, folder
);
1629 case ShellMenuImportPatch
:
1630 AddPathFileCommand(gitCmd
, L
"importpatch", paths
, folder
);
1632 case ShellMenuImportPatchDrop
:
1633 AddPathFileDropCommand(gitCmd
, L
"importpatch", paths
, folder
);
1635 case ShellMenuFetch
:
1636 AddPathFileCommand(gitCmd
, L
"fetch", paths
, folder
, true);
1638 case ShellMenuLFSLocks
:
1639 AddPathFileCommand(gitCmd
, L
"lfslocks", paths
, folder
);
1641 case ShellMenuLFSLock
:
1642 AddPathFileCommand(gitCmd
, L
"lfslock", paths
, folder
);
1644 case ShellMenuLFSUnlock
:
1645 AddPathFileCommand(gitCmd
, L
"lfsunlock", paths
, folder
);
1647 case ShellMenuWorktree
:
1648 AddPathCommand(gitCmd
, L
"worktreelist", false, paths
, folder
);
1650 case ShellMenuDropNewWorktree
:
1651 AddPathFileDropCommand(gitCmd
, L
"dropnewworktree", paths
, folder
);
1656 } // switch (id_it->second)
1657 if (!gitCmd
.empty())
1659 gitCmd
+= L
" /hwnd:";
1660 wchar_t buf
[30] = { 0 };
1661 swprintf_s(buf
, L
"%p", static_cast<void*>(hParent
));
1663 RunCommand(appDir
+ L
"TortoiseGitProc.exe", gitCmd
, L
"TortoiseGitProc launch failed", site
);
1667 // This is for the status bar and things like that:
1668 STDMETHODIMP
CShellExt::GetCommandString(UINT_PTR idCmd
, UINT uFlags
, UINT FAR
* /*reserved*/, LPSTR pszName
, UINT cchMax
)
1670 PreserveChdir preserveChdir
;
1671 //do we know the id?
1672 std::map
<UINT_PTR
, UINT_PTR
>::const_iterator id_it
= myIDMap
.lower_bound(idCmd
);
1673 if (id_it
== myIDMap
.end() || id_it
->first
!= idCmd
)
1675 return E_INVALIDARG
; //no, we don't
1679 HRESULT hr
= E_INVALIDARG
;
1681 MAKESTRING(IDS_MENUDESCDEFAULT
);
1683 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1685 if (menuInfo
[menuIndex
].command
== static_cast<GitCommands
>(id_it
->second
))
1687 MAKESTRING(menuInfo
[menuIndex
].menuDescID
);
1693 const wchar_t* desc
= stringtablebuffer
;
1698 std::string help
= WideToMultibyte(desc
);
1699 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1705 std::wstring help
= desc
;
1706 lstrcpynW(reinterpret_cast<LPWSTR
>(pszName
), help
.c_str(), cchMax
- 1);
1712 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1713 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1715 std::string help
= WideToMultibyte(verb_id_it
->second
);
1716 lstrcpynA(pszName
, help
.c_str(), cchMax
- 1);
1723 const auto verb_id_it
= myVerbsIDMap
.lower_bound(idCmd
);
1724 if (verb_id_it
!= myVerbsIDMap
.end() && verb_id_it
->first
== idCmd
)
1726 std::wstring help
= verb_id_it
->second
;
1727 CTraceToOutputDebugString::Instance()(__FUNCTION__
": verb : %ws\n", help
.c_str());
1728 lstrcpynW(reinterpret_cast<LPWSTR
>(pszName
), help
.c_str(), cchMax
- 1);
1737 STDMETHODIMP
CShellExt::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
1740 return HandleMenuMsg2(uMsg
, wParam
, lParam
, &res
);
1743 STDMETHODIMP
CShellExt::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*pResult
)
1745 PreserveChdir preserveChdir
;
1755 case WM_MEASUREITEM
:
1757 auto lpmis
= reinterpret_cast<MEASUREITEMSTRUCT
*>(lParam
);
1760 lpmis
->itemWidth
= 16;
1761 lpmis
->itemHeight
= 16;
1768 auto lpdis
= reinterpret_cast<DRAWITEMSTRUCT
*>(lParam
);
1769 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
1770 return S_OK
; //not for a menu
1771 resource
= GetMenuTextFromResource(static_cast<int>(myIDMap
[lpdis
->itemID
]));
1774 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
1775 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
1776 CAutoIcon hIcon
= LoadIconEx(g_hResInst
, resource
, iconWidth
, iconHeight
);
1779 DrawIconEx(lpdis
->hDC
,
1781 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- iconHeight
) / 2,
1782 hIcon
, iconWidth
, iconHeight
,
1783 0, nullptr, DI_NORMAL
);
1790 if (HIWORD(wParam
) != MF_POPUP
)
1792 int nChar
= LOWORD(wParam
);
1793 if (_istascii(static_cast<wint_t>(nChar
)) && _istupper(static_cast<wint_t>(nChar
)))
1794 nChar
= tolower(nChar
);
1795 // we have the char the user pressed, now search that char in all our
1797 std::vector
<UINT_PTR
> accmenus
;
1798 for (auto It
= mySubMenuMap
.cbegin(); It
!= mySubMenuMap
.cend(); ++It
)
1800 LPCWSTR resource
= GetMenuTextFromResource(static_cast<int>(mySubMenuMap
[It
->first
]));
1803 szItem
= stringtablebuffer
;
1804 wchar_t* amp
= wcschr(szItem
, L
'&');
1808 int ampChar
= LOWORD(*amp
);
1809 if (_istascii(static_cast<wint_t>(ampChar
)) && _istupper(static_cast<wint_t>(ampChar
)))
1810 ampChar
= tolower(ampChar
);
1811 if (ampChar
== nChar
)
1813 // yep, we found a menu which has the pressed key
1814 // as an accelerator. Add that menu to the list to
1816 accmenus
.push_back(It
->first
);
1819 if (accmenus
.empty())
1821 // no menu with that accelerator key.
1822 *pResult
= MAKELONG(0, MNC_IGNORE
);
1825 if (accmenus
.size() == 1)
1827 // Only one menu with that accelerator key. We're lucky!
1828 // So just execute that menu entry.
1829 *pResult
= MAKELONG(accmenus
[0], MNC_EXECUTE
);
1832 if (accmenus
.size() > 1)
1834 // we have more than one menu item with this accelerator key!
1836 mif
.cbSize
= sizeof(MENUITEMINFO
);
1837 mif
.fMask
= MIIM_STATE
;
1838 for (auto it
= accmenus
.cbegin(); it
!= accmenus
.cend(); ++it
)
1840 GetMenuItemInfo(reinterpret_cast<HMENU
>(lParam
), static_cast<UINT
>(*it
), TRUE
, &mif
);
1841 if (mif
.fState
== MFS_HILITE
)
1843 // this is the selected item, so select the next one
1845 if (it
== accmenus
.end())
1846 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1848 *pResult
= MAKELONG(*it
, MNC_SELECT
);
1852 *pResult
= MAKELONG(accmenus
[0], MNC_SELECT
);
1863 LPCWSTR
CShellExt::GetMenuTextFromResource(int id
)
1865 wchar_t textbuf
[255] = { 0 };
1866 LPCWSTR resource
= nullptr;
1867 unsigned __int64 layout
= g_ShellCache
.GetMenuLayout();
1871 while (menuInfo
[menuIndex
].command
!= ShellMenuLastEntry
)
1873 if (menuInfo
[menuIndex
].command
== id
)
1875 MAKESTRING(menuInfo
[menuIndex
].menuTextID
);
1876 resource
= MAKEINTRESOURCE(menuInfo
[menuIndex
].iconID
);
1879 case ShellSubMenuMultiple
:
1880 case ShellSubMenuLink
:
1881 case ShellSubMenuFolder
:
1882 case ShellSubMenuFile
:
1887 space
= (layout
& menuInfo
[menuIndex
].menuID
) ? 0 : 6;
1888 if (layout
& menuInfo
[menuIndex
].menuID
)
1890 wcscpy_s(textbuf
, L
"Git ");
1891 wcscat_s(textbuf
, stringtablebuffer
);
1892 wcscpy_s(stringtablebuffer
, textbuf
);
1903 bool CShellExt::IsIllegalFolder(const std::wstring
& folder
)
1905 static const GUID code
[] = {
1906 FOLDERID_RecycleBinFolder
,
1909 FOLDERID_CommonStartMenu
,
1910 FOLDERID_NetworkFolder
,
1911 FOLDERID_ConnectionsFolder
,
1912 FOLDERID_ControlPanelFolder
,
1917 FOLDERID_InternetFolder
,
1918 FOLDERID_InternetCache
,
1920 FOLDERID_NetworkFolder
,
1921 FOLDERID_PrintersFolder
,
1927 for (int i
= 0; i
< _countof(code
); i
++)
1929 CComHeapPtr
<WCHAR
> pszPath
;
1930 if (SHGetKnownFolderPath(code
[i
], 0, nullptr, &pszPath
) != S_OK
)
1934 if (wcscmp(pszPath
, folder
.c_str()) == 0)
1940 bool CShellExt::InsertLFSSubmenu(UINT
& idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
& indexMenu
, int& indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT uFlags
)
1942 static MenuInfo infos
[] = {
1943 { ShellMenuLFSLocks
, 0, IDI_REPOBROWSE
, IDS_MENULFSLOCKS
, IDS_MENUDESCLFSLOCKS
, { ITEMIS_FOLDERINGIT
| ITEMIS_ONLYONE
, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
1944 { ShellMenuLFSLock
, 0, IDI_LFSLOCK
, IDS_MENULFSLOCK
, IDS_MENUDESCLFSLOCK
, { ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
, ITEMIS_FOLDER
}, { 0, 0 }, { 0, 0 }, { 0, 0 } },
1945 { ShellMenuLFSUnlock
, 0, IDI_LFSUNLOCK
, IDS_MENULFSUNLOCK
, IDS_MENUDESCLFSUNLOCK
, { ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
, ITEMIS_FOLDER
}, { 0, 0 }, { 0, 0 }, { 0, 0 } },
1948 if (folder_
.empty() && files_
.empty())
1951 CTGitPath askedpath
;
1952 askedpath
.SetFromWin(folder_
.empty() ? files_
.front().c_str() : folder_
.c_str());
1953 if (!askedpath
.HasLFS())
1956 HMENU lfssubmenu
= hMenu
? CreateMenu() : nullptr;
1957 int indexlfssub
= 0;
1958 bool anyMenu
= false;
1960 for (const auto& info
: infos
)
1962 if (ShouldInsertItem(info
))
1964 InsertGitMenu(false, lfssubmenu
, indexlfssub
++, idCmd
++, info
.menuTextID
, bShowIcons
? info
.iconID
: 0, idCmdFirst
, info
.command
, uFlags
);
1972 MENUITEMINFO menuiteminfo
= { 0 };
1973 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
1974 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
| MIIM_BITMAP
;
1975 menuiteminfo
.fType
= MFT_STRING
;
1976 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, IDI_LFS
);
1977 menuiteminfo
.hSubMenu
= lfssubmenu
;
1978 menuiteminfo
.wID
= idCmd
;
1980 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
1981 GetMenuTextFromResource(ShellMenuLFSMenu
);
1982 menuiteminfo
.dwTypeData
= stringtablebuffer
;
1983 menuiteminfo
.cch
= static_cast<UINT
>(min(wcslen(menuiteminfo
.dwTypeData
), static_cast<size_t>(UINT_MAX
)));
1986 InsertMenuItem((topmenu
& MENULFS
) ? hMenu
: subMenu
, (topmenu
& MENULFS
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
1987 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuLFSMenu
;
1988 myIDMap
[idCmd
++] = ShellMenuLFSMenu
;
1993 bool CShellExt::InsertIgnoreSubmenus(UINT
&idCmd
, UINT idCmdFirst
, HMENU hMenu
, HMENU subMenu
, UINT
&indexMenu
, int &indexSubMenu
, unsigned __int64 topmenu
, bool bShowIcons
, UINT
/*uFlags*/)
1995 HMENU ignoresubmenu
= nullptr;
1996 int indexignoresub
= 0;
1997 bool bShowIgnoreMenu
= false;
1998 wchar_t maskbuf
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
1999 wchar_t ignorepath
[MAX_PATH
] = {0}; // MAX_PATH is ok, since this only holds a filename
2000 std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>> exCmds
;
2003 const UINT icon
= bShowIcons
? IDI_IGNORE
: 0;
2005 auto I
= files_
.cbegin();
2006 if (wcsrchr(I
->c_str(), L
'\\'))
2007 wcscpy_s(ignorepath
, wcsrchr(I
->c_str(), L
'\\') + 1);
2009 wcscpy_s(ignorepath
, I
->c_str());
2010 if ((itemStates
& ITEMIS_IGNORED
) && (!ignoredprops
.empty()))
2012 // check if the item name is ignored or the mask
2014 const size_t pathLength
= wcslen(ignorepath
);
2015 while ( (p
=ignoredprops
.find( ignorepath
,p
)) != -1 )
2017 if ((p
== 0 || ignoredprops
[p
- 1] == wchar_t('\n'))
2018 && (p
+ pathLength
== ignoredprops
.length() || ignoredprops
[p
+ pathLength
+ 1] == wchar_t('\n')))
2026 ignoresubmenu
= hMenu
? CreateMenu() : nullptr;
2028 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2030 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuUnIgnore
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
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
] = ShellMenuUnIgnore
;
2037 myIDMap
[idCmd
++] = ShellMenuUnIgnore
;
2038 bShowIgnoreMenu
= true;
2040 wcscpy_s(maskbuf
, L
"*");
2041 if (wcsrchr(ignorepath
, L
'.'))
2043 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2044 p
= ignoredprops
.find(maskbuf
);
2046 ((ignoredprops
.compare(maskbuf
) == 0) || (ignoredprops
.find(L
'\n', p
) == p
+ wcslen(maskbuf
) + 1) || (ignoredprops
.rfind(L
'\n', p
) == p
- 1)))
2049 ignoresubmenu
= CreateMenu();
2051 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2053 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(maskbuf
, 0, ShellMenuUnIgnoreCaseSensitive
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2054 auto verb
= std::wstring(maskbuf
);
2055 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2056 myVerbsMap
[verb
] = idCmd
;
2057 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2058 myVerbsIDMap
[idCmd
] = verb
;
2059 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreCaseSensitive
;
2060 myIDMap
[idCmd
++] = ShellMenuUnIgnoreCaseSensitive
;
2061 bShowIgnoreMenu
= true;
2065 else if ((itemStates
& ITEMIS_IGNORED
) == 0)
2067 bShowIgnoreMenu
= true;
2068 ignoresubmenu
= CreateMenu();
2069 if (itemStates
& ITEMIS_ONLYONE
)
2071 if (itemStates
& ITEMIS_INGIT
)
2074 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2076 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuDeleteIgnore
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2077 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2078 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2080 wcscpy_s(maskbuf
, L
"*");
2081 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
2083 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2085 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2087 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(maskbuf
, 0, ShellMenuDeleteIgnoreCaseSensitive
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2088 auto verb
= std::wstring(maskbuf
);
2089 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2090 myVerbsMap
[verb
] = idCmd
;
2091 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2092 myVerbsIDMap
[idCmd
] = verb
;
2093 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2094 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2100 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2102 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuIgnore
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2103 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2104 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2106 wcscpy_s(maskbuf
, L
"*");
2107 if (!(itemStates
& ITEMIS_FOLDER
) && wcsrchr(ignorepath
, L
'.'))
2109 wcscat_s(maskbuf
, wcsrchr(ignorepath
, L
'.'));
2111 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, maskbuf
);
2113 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(maskbuf
, 0, ShellMenuIgnoreCaseSensitive
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2114 auto verb
= std::wstring(maskbuf
);
2115 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2116 myVerbsMap
[verb
] = idCmd
;
2117 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2118 myVerbsIDMap
[idCmd
] = verb
;
2119 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2120 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2126 // note: as of Windows 7, the shell does not pass more than 16 items from a multiselection
2127 // in the Initialize() call before the QueryContextMenu() call. Which means even if the user
2128 // has selected more than 16 files, we won't know about that here.
2129 // Note: after QueryContextMenu() exits, Initialize() is called again with all selected files.
2130 if (itemStates
& ITEMIS_INGIT
)
2132 if (files_
.size() >= 16)
2134 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE2
);
2135 wcscpy_s(ignorepath
, stringtablebuffer
);
2139 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLE
);
2140 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2143 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2145 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuDeleteIgnore
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2146 auto verb
= std::wstring(ignorepath
);
2147 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2148 myVerbsMap
[verb
] = idCmd
;
2149 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2150 myVerbsIDMap
[idCmd
] = verb
;
2151 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnore
;
2152 myIDMap
[idCmd
++] = ShellMenuDeleteIgnore
;
2154 if (files_
.size() >= 16)
2156 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK2
);
2157 wcscpy_s(ignorepath
, stringtablebuffer
);
2161 MAKESTRING(IDS_MENUDELETEIGNOREMULTIPLEMASK
);
2162 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2165 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2167 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuDeleteIgnoreCaseSensitive
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2168 verb
= std::wstring(ignorepath
);
2169 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2170 myVerbsMap
[verb
] = idCmd
;
2171 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2172 myVerbsIDMap
[idCmd
] = verb
;
2173 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuDeleteIgnoreCaseSensitive
;
2174 myIDMap
[idCmd
++] = ShellMenuDeleteIgnoreCaseSensitive
;
2178 if (files_
.size() >= 16)
2180 MAKESTRING(IDS_MENUIGNOREMULTIPLE2
);
2181 wcscpy_s(ignorepath
, stringtablebuffer
);
2185 MAKESTRING(IDS_MENUIGNOREMULTIPLE
);
2186 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2189 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2191 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuIgnore
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2192 auto verb
= std::wstring(ignorepath
);
2193 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2194 myVerbsMap
[verb
] = idCmd
;
2195 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2196 myVerbsIDMap
[idCmd
] = verb
;
2197 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnore
;
2198 myIDMap
[idCmd
++] = ShellMenuIgnore
;
2200 if (files_
.size() >= 16)
2202 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK2
);
2203 wcscpy_s(ignorepath
, stringtablebuffer
);
2207 MAKESTRING(IDS_MENUIGNOREMULTIPLEMASK
);
2208 swprintf_s(ignorepath
, stringtablebuffer
, files_
.size());
2211 InsertMenu(ignoresubmenu
, indexignoresub
++, MF_BYPOSITION
| MF_STRING
, idCmd
, ignorepath
);
2213 exCmds
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(ignorepath
, 0, ShellMenuIgnoreCaseSensitive
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2214 verb
= std::wstring(ignorepath
);
2215 myVerbsMap
[verb
] = idCmd
- idCmdFirst
;
2216 myVerbsMap
[verb
] = idCmd
;
2217 myVerbsIDMap
[idCmd
- idCmdFirst
] = verb
;
2218 myVerbsIDMap
[idCmd
] = verb
;
2219 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreCaseSensitive
;
2220 myIDMap
[idCmd
++] = ShellMenuIgnoreCaseSensitive
;
2225 if (bShowIgnoreMenu
)
2227 MENUITEMINFO menuiteminfo
= { 0 };
2228 menuiteminfo
.cbSize
= sizeof(menuiteminfo
);
2229 menuiteminfo
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_SUBMENU
| MIIM_DATA
| MIIM_STRING
;
2232 menuiteminfo
.fMask
|= MIIM_BITMAP
;
2233 menuiteminfo
.hbmpItem
= m_iconBitmapUtils
.IconToBitmapPARGB32(g_hResInst
, icon
);
2235 menuiteminfo
.fType
= MFT_STRING
;
2236 menuiteminfo
.hSubMenu
= ignoresubmenu
;
2237 menuiteminfo
.wID
= idCmd
;
2238 SecureZeroMemory(stringtablebuffer
, sizeof(stringtablebuffer
));
2239 if (itemStates
& ITEMIS_IGNORED
)
2240 GetMenuTextFromResource(ShellMenuUnIgnoreSub
);
2241 else if (itemStates
& ITEMIS_INGIT
)
2242 GetMenuTextFromResource(ShellMenuDeleteIgnoreSub
);
2244 GetMenuTextFromResource(ShellMenuIgnoreSub
);
2245 menuiteminfo
.dwTypeData
= stringtablebuffer
;
2246 menuiteminfo
.cch
= static_cast<UINT
>(min(wcslen(menuiteminfo
.dwTypeData
), static_cast<size_t>(UINT_MAX
)));
2249 InsertMenuItem((topmenu
& MENUIGNORE
) ? hMenu
: subMenu
, (topmenu
& MENUIGNORE
) ? indexMenu
++ : indexSubMenu
++, TRUE
, &menuiteminfo
);
2252 m_explorerCommands
.push_back(Microsoft::WRL::Make
<CExplorerCommand
>(L
"", 0, ShellSeparator
, static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
)), uuidSource
, itemStates
, itemStatesFolder
, files_
, std::vector
<Microsoft::WRL::ComPtr
<CExplorerCommand
>>(), m_site
));
2253 for (const auto& cmd
: exCmds
)
2255 m_explorerCommands
.push_back(cmd
);
2256 std::wstring prep
= stringtablebuffer
;
2258 m_explorerCommands
.back()->PrependTitleWith(prep
);
2260 // currently, explorer does not support subcommands which their own subcommands. Once it does,
2261 // use the line below instead of the ones above
2262 //m_explorerCommands.push_back(CExplorerCommand(stringTableBuffer, icon, ShellMenuUnIgnoreSub, GetAppDirectory(), uuidSource, itemStates, itemStatesFolder, m_files, exCmds));
2264 if (itemStates
& ITEMIS_IGNORED
)
2266 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuUnIgnoreSub
;
2267 myIDMap
[idCmd
++] = ShellMenuUnIgnoreSub
;
2271 myIDMap
[idCmd
- idCmdFirst
] = ShellMenuIgnoreSub
;
2272 myIDMap
[idCmd
++] = ShellMenuIgnoreSub
;
2275 return bShowIgnoreMenu
;
2278 void CShellExt::RunCommand(const std::wstring
& path
, const std::wstring
& command
, LPCWSTR errorMessage
, Microsoft::WRL::ComPtr
<IUnknown
> site
)
2282 Microsoft::WRL::ComPtr
<IServiceProvider
> serviceProvider
;
2283 if (SUCCEEDED(site
.As(&serviceProvider
)))
2285 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IServiceProvider\n");
2286 Microsoft::WRL::ComPtr
<IShellBrowser
> shellBrowser
;
2287 if (SUCCEEDED(serviceProvider
->QueryService(SID_SShellBrowser
, IID_IShellBrowser
, &shellBrowser
)))
2289 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IShellBrowser\n");
2290 Microsoft::WRL::ComPtr
<IShellView
> shellView
;
2291 if (SUCCEEDED(shellBrowser
->QueryActiveShellView(&shellView
)))
2293 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IShellView\n");
2294 Microsoft::WRL::ComPtr
<IDispatch
> spdispView
;
2295 if (SUCCEEDED(shellView
->GetItemObject(SVGIO_BACKGROUND
, IID_PPV_ARGS(&spdispView
))))
2297 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IDispatch\n");
2298 Microsoft::WRL::ComPtr
<IShellFolderViewDual
> spFolderView
;
2299 if (SUCCEEDED(spdispView
.As(&spFolderView
)))
2301 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IShellFolderViewDual\n");
2302 Microsoft::WRL::ComPtr
<IDispatch
> spdispShell
;
2303 if (SUCCEEDED(spFolderView
->get_Application(&spdispShell
)))
2305 Microsoft::WRL::ComPtr
<IShellDispatch2
> spdispShell2
;
2306 if (SUCCEEDED(spdispShell
.As(&spdispShell2
)))
2308 // without this, the launched app is not moved to the foreground
2309 AllowSetForegroundWindow(ASFW_ANY
);
2311 if (SUCCEEDED(spdispShell2
->ShellExecute(_bstr_t
{ path
.c_str() },
2312 _variant_t
{ command
.c_str() },
2314 _variant_t
{ L
"open" },
2315 _variant_t
{ SW_NORMAL
})))
2327 if (CCreateProcessHelper::CreateProcessDetached(path
.c_str(), command
.c_str()))
2329 // process started - exit
2333 MessageBox(nullptr, CFormatMessageWrapper(), errorMessage
, MB_OK
| MB_ICONERROR
);
2336 bool CShellExt::ShouldInsertItem(const MenuInfo
& item
) const
2338 return ShouldEnableMenu(item
.first
) || ShouldEnableMenu(item
.second
) ||
2339 ShouldEnableMenu(item
.third
) || ShouldEnableMenu(item
.fourth
);
2342 bool CShellExt::ShouldEnableMenu(const YesNoPair
& pair
) const
2344 if (pair
.yes
&& pair
.no
)
2346 if (((pair
.yes
& itemStates
) == pair
.yes
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2349 else if ((pair
.yes
) && ((pair
.yes
& itemStates
) == pair
.yes
))
2351 else if ((pair
.no
) && ((pair
.no
& (~itemStates
)) == pair
.no
))
2357 HRESULT __stdcall
CShellExt::GetTitle(IShellItemArray
* /*psiItemArray*/, LPWSTR
* ppszName
)
2359 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetTitle\n");
2360 SHStrDupW(L
"TortoiseGit", ppszName
);
2364 HRESULT __stdcall
CShellExt::GetIcon(IShellItemArray
* /*psiItemArray*/, LPWSTR
* ppszIcon
)
2366 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetIcon\n");
2367 std::wstring iconPath
= static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory(g_hmodThisDll
));
2368 iconPath
+= L
"TortoiseGitProc.exe,-";
2369 iconPath
+= std::to_wstring(IDI_APP
);
2370 SHStrDupW(iconPath
.c_str(), ppszIcon
);
2374 HRESULT __stdcall
CShellExt::GetToolTip(IShellItemArray
* /*psiItemArray*/, LPWSTR
* ppszInfotip
)
2376 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetToolTip\n");
2377 *ppszInfotip
= nullptr;
2381 HRESULT __stdcall
CShellExt::GetCanonicalName(GUID
* pguidCommandName
)
2383 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetCanonicalName\n");
2384 *pguidCommandName
= GUID_NULL
;
2388 HRESULT __stdcall
CShellExt::GetState(IShellItemArray
* psiItemArray
, BOOL fOkToBeSlow
, EXPCMDSTATE
* pCmdState
)
2390 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetState\n");
2391 *pCmdState
= ECS_ENABLED
;
2392 Microsoft::WRL::ComPtr
<IShellItemArray
> ownItemArray
;
2395 Microsoft::WRL::ComPtr
<IOleWindow
> oleWindow
;
2396 m_site
.As(&oleWindow
);
2399 // We don't want to show the menu on the classic context menu.
2400 // The classic menu provides an IOleWindow, but the main context
2401 // menu in Win11 does not, except on the left tree view.
2402 // So we check the window class name: if it's "NamespaceTreeControl",
2403 // then we're dealing with the main context menu of the tree view.
2404 // If it's not, then we're dealing with the classic context menu
2405 // and there we hide this menu entry.
2406 HWND hWnd
= nullptr;
2407 oleWindow
->GetWindow(&hWnd
);
2408 wchar_t szWndClassName
[MAX_PATH
] = { 0 };
2409 GetClassName(hWnd
, szWndClassName
, _countof(szWndClassName
));
2410 if (wcscmp(szWndClassName
, L
"NamespaceTreeControl"))
2412 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetState - hidden\n");
2413 *pCmdState
= ECS_HIDDEN
;
2421 using SetThreadDpiAwarenessContextProc
= DPI_AWARENESS_CONTEXT(WINAPI
*)(DPI_AWARENESS_CONTEXT
);
2422 SetThreadDpiAwarenessContextProc SetThreadDpiAwarenessContext
= reinterpret_cast<SetThreadDpiAwarenessContextProc
>(GetProcAddress(GetModuleHandle(L
"user32"), "SetThreadDpiAwarenessContext"));
2423 DPI_AWARENESS_CONTEXT context
= DPI_AWARENESS_CONTEXT_UNAWARE
;
2424 // the shell disables dpi awareness for extensions, so enable them explicitly
2425 if (SetThreadDpiAwarenessContext
)
2426 context
= SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
);
2427 Microsoft::WRL::ComPtr
<INameSpaceTreeControl
> nameSpaceTreeCtrl
;
2428 oleWindow
.As(&nameSpaceTreeCtrl
);
2429 if (nameSpaceTreeCtrl
)
2431 // we do a hit test on the tree view to get the right-clicked item.
2432 // however this only works if the menu shows up due to a right-click.
2433 // if the menu is shown because of a key press (right windows key),
2434 // then this will get the wrong item if the mouse pointer is somewhere
2435 // over the tree view while the key is clicked!
2436 // if the mouse pointer is NOT over the tree view when the menu is brought up
2437 // via keyboard, then this works fine.
2438 auto msgPos
= GetMessagePos();
2440 msgPoint
.x
= GET_X_LPARAM(msgPos
);
2441 msgPoint
.y
= GET_Y_LPARAM(msgPos
);
2442 POINT pt
= msgPoint
;
2443 ScreenToClient(hWnd
, &pt
);
2444 Microsoft::WRL::ComPtr
<IShellItem
> shellItem
;
2446 nameSpaceTreeCtrl
->HitTest(&pt
, &shellItem
);
2448 SHCreateShellItemArrayFromShellItem(shellItem
.Get(), IID_IShellItemArray
, &ownItemArray
);
2450 nameSpaceTreeCtrl
->GetSelectedItems(&ownItemArray
);
2452 if (SetThreadDpiAwarenessContext
)
2453 SetThreadDpiAwarenessContext(context
);
2456 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetState - hidden\n");
2457 *pCmdState
= ECS_HIDDEN
;
2461 psiItemArray
= ownItemArray
.Get();
2470 Initialize(nullptr, nullptr, nullptr);
2471 Microsoft::WRL::ComPtr
<IShellItemArray
> itemArray
;
2474 // context menu for a folder background (no selection),
2475 // so try to get the current path of the explorer window instead
2476 auto path
= ExplorerViewPath(m_site
);
2479 *pCmdState
= ECS_HIDDEN
;
2482 PIDLIST_ABSOLUTE pidl
{};
2483 if (SUCCEEDED(SHParseDisplayName(path
.c_str(), nullptr, &pidl
, 0, nullptr)))
2485 if (SUCCEEDED(SHCreateShellItemArrayFromIDLists(1, const_cast<LPCITEMIDLIST
*>(&pidl
), itemArray
.GetAddressOf())))
2489 psiItemArray
= itemArray
.Get();
2496 IDataObject
* pDataObj
= nullptr;
2497 if (SUCCEEDED(psiItemArray
->BindToHandler(nullptr, BHID_DataObject
, IID_IDataObject
, reinterpret_cast<void**>(&pDataObj
))))
2499 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: Initialize from GetState()\n");
2501 Initialize(nullptr, pDataObj
, nullptr);
2502 pDataObj
->Release();
2506 *pCmdState
= ECS_HIDDEN
;
2509 if (g_ShellCache
.HideMenusForUnversionedItems() && (GetKeyState(VK_SHIFT
) & 0x8000) == 0)
2511 if ((itemStates
& (ITEMIS_INGIT
| ITEMIS_INVERSIONEDFOLDER
| ITEMIS_FOLDERINGIT
)) == 0)
2512 *pCmdState
= ECS_HIDDEN
;
2514 if (*pCmdState
!= ECS_HIDDEN
)
2516 m_explorerCommands
.clear();
2517 QueryContextMenu(nullptr, 0, 0, 0, CMF_EXTENDEDVERBS
| CMF_NORMAL
);
2518 if (m_explorerCommands
.empty())
2519 *pCmdState
= ECS_HIDDEN
;
2525 HRESULT __stdcall
CShellExt::Invoke(IShellItemArray
* /*psiItemArray*/, IBindCtx
* /*pbc*/)
2530 HRESULT __stdcall
CShellExt::GetFlags(EXPCMDFLAGS
* pFlags
)
2532 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: GetFlags\n");
2533 *pFlags
= ECF_HASSUBCOMMANDS
;
2537 HRESULT __stdcall
CShellExt::EnumSubCommands(IEnumExplorerCommand
** ppEnum
)
2539 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Shell :: EnumSubCommands %lld\n", m_explorerCommands
.size());
2540 m_explorerCommands
.clear();
2541 QueryContextMenu(nullptr, 0, 0, 0, CMF_EXTENDEDVERBS
| CMF_NORMAL
);
2542 *ppEnum
= Microsoft::WRL::Make
<CExplorerCommandEnum
>(m_explorerCommands
).Detach();
2546 std::wstring
CShellExt::ExplorerViewPath(const Microsoft::WRL::ComPtr
<IUnknown
>& site
)
2548 CTraceToOutputDebugString::Instance()(__FUNCTION__
"\n");
2552 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got site\n");
2553 Microsoft::WRL::ComPtr
<IServiceProvider
> serviceProvider
;
2554 if (SUCCEEDED(site
.As(&serviceProvider
)))
2556 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IServiceProvider\n");
2557 Microsoft::WRL::ComPtr
<IShellBrowser
> shellBrowser
;
2558 if (SUCCEEDED(serviceProvider
->QueryService(SID_SShellBrowser
, IID_IShellBrowser
, &shellBrowser
)))
2560 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IShellBrowser\n");
2561 Microsoft::WRL::ComPtr
<IShellView
> shellView
;
2562 if (SUCCEEDED(shellBrowser
->QueryActiveShellView(&shellView
)))
2564 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IShellView\n");
2565 Microsoft::WRL::ComPtr
<IFolderView
> folderView
;
2566 if (SUCCEEDED(shellView
.As(&folderView
)))
2568 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IFolderView\n");
2569 Microsoft::WRL::ComPtr
<IPersistFolder2
> persistFolder
;
2570 if (SUCCEEDED(folderView
->GetFolder(IID_IPersistFolder2
, (LPVOID
*)&persistFolder
)))
2572 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got IPersistFolder2\n");
2573 PIDLIST_ABSOLUTE curFolder
;
2574 if (SUCCEEDED(persistFolder
->GetCurFolder(&curFolder
)))
2576 CTraceToOutputDebugString::Instance()(__FUNCTION__
": got GetCurFolder\n");
2577 wchar_t buf
[MAX_PATH
] = { 0 };
2578 // find the path of the folder
2579 if (SHGetPathFromIDList(curFolder
, buf
))
2581 CTraceToOutputDebugString::Instance()(__FUNCTION__ L
": got SHGetPathFromIDList : %s\n", buf
);
2584 CoTaskMemFree(curFolder
);