Don't set an exception filter for the shell extension
[TortoiseGit.git] / src / TortoiseShell / IconOverlay.cpp
blobb2232cf5a892fe78c8dcf92b3feabbd6497a23dd
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2013, 2015-2018 - TortoiseGit
4 // Copyright (C) 2003-2008, 2017 - TortoiseSVN
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 "Guids.h"
23 #include "PreserveChdir.h"
24 #include "UnicodeUtils.h"
25 #include "GitStatus.h"
26 #include "../TGitCache/CacheInterface.h"
27 #include "GitAdminDir.h"
28 #include "StringUtils.h"
30 // "The Shell calls IShellIconOverlayIdentifier::GetOverlayInfo to request the
31 // location of the handler's icon overlay. The icon overlay handler returns
32 // the name of the file containing the overlay image, and its index within
33 // that file. The Shell then adds the icon overlay to the system image list."
35 STDMETHODIMP CShellExt::GetOverlayInfo(LPWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags)
37 if (!pwszIconFile)
38 return E_POINTER;
39 if (!pIndex)
40 return E_POINTER;
41 if (!pdwFlags)
42 return E_POINTER;
43 if(cchMax < 1)
44 return E_INVALIDARG;
46 // Set "out parameters" since we return S_OK later.
47 *pwszIconFile = 0;
48 *pIndex = 0;
49 *pdwFlags = 0;
51 // Now here's where we can find out if due to lack of enough overlay
52 // slots some of our overlays won't be shown.
53 // To do that we have to mark every overlay handler that's successfully
54 // loaded, so we can later check if some are missing
55 switch (m_State)
57 case FileStateVersioned : g_normalovlloaded = true; break;
58 case FileStateModified : g_modifiedovlloaded = true; break;
59 case FileStateConflict : g_conflictedovlloaded = true; break;
60 case FileStateDeleted : g_deletedovlloaded = true; break;
61 case FileStateReadOnly : g_readonlyovlloaded = true; break;
62 case FileStateLockedOverlay : g_lockedovlloaded = true; break;
63 case FileStateAddedOverlay : g_addedovlloaded = true; break;
64 case FileStateIgnoredOverlay : g_ignoredovlloaded = true; break;
65 case FileStateUnversionedOverlay : g_unversionedovlloaded = true; break;
68 // we don't have to set the icon file and/or the index here:
69 // the icons are handled by the TortoiseOverlays dll.
70 return S_OK;
73 STDMETHODIMP CShellExt::GetPriority(int *pPriority)
75 if (!pPriority)
76 return E_POINTER;
78 switch (m_State)
80 case FileStateConflict:
81 *pPriority = 0;
82 break;
83 case FileStateModified:
84 *pPriority = 1;
85 break;
86 case FileStateDeleted:
87 *pPriority = 2;
88 break;
89 case FileStateReadOnly:
90 *pPriority = 3;
91 break;
92 case FileStateLockedOverlay:
93 *pPriority = 4;
94 break;
95 case FileStateAddedOverlay:
96 *pPriority = 5;
97 break;
98 case FileStateVersioned:
99 *pPriority = 6;
100 break;
101 default:
102 *pPriority = 100;
103 return S_FALSE;
105 return S_OK;
108 // "Before painting an object's icon, the Shell passes the object's name to
109 // each icon overlay handler's IShellIconOverlayIdentifier::IsMemberOf
110 // method. If a handler wants to have its icon overlay displayed,
111 // it returns S_OK. The Shell then calls the handler's
112 // IShellIconOverlayIdentifier::GetOverlayInfo method to determine which icon
113 // to display."
115 STDMETHODIMP CShellExt::IsMemberOf(LPCWSTR pwszPath, DWORD /*dwAttrib*/)
117 if (!pwszPath)
118 return E_INVALIDARG;
119 const TCHAR* pPath = pwszPath;
120 // the shell sometimes asks overlays for invalid paths, e.g. for network
121 // printers (in that case the path is "0", at least for me here).
122 if (wcslen(pPath) < 2)
123 return S_FALSE;
124 PreserveChdir preserveChdir;
125 git_wc_status_kind status = git_wc_status_none;
126 bool readonlyoverlay = false;
127 bool lockedoverlay = false;
129 // since the shell calls each and every overlay handler with the same filepath
130 // we use a small 'fast' cache of just one path here.
131 // To make sure that cache expires, clear it as soon as one handler is used.
133 AutoLocker lock(g_csGlobalCOMGuard);
134 if (wcscmp(pPath, g_filepath.c_str()) == 0)
136 status = g_filestatus;
137 readonlyoverlay = g_readonlyoverlay;
138 lockedoverlay = g_lockedoverlay;
140 else
142 if (!g_ShellCache.IsPathAllowed(pPath))
144 if ((m_State == FileStateVersioned) && g_ShellCache.ShowExcludedAsNormal() &&
145 (PathGetDriveNumber(pPath)>1) &&
146 PathIsDirectory(pPath) && g_ShellCache.HasGITAdminDir(pPath, true))
148 return S_OK;
150 return S_FALSE;
153 auto cacheType = g_ShellCache.GetCacheType();
154 if (g_ShellCache.IsOnlyNonElevated() && g_ShellCache.IsProcessElevated())
156 cacheType = ShellCache::none;
157 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": elevated overlays blocked\n");
159 switch (cacheType)
161 case ShellCache::exe:
163 CTGitPath tpath(pPath);
164 if(!tpath.HasAdminDir())
166 status = git_wc_status_none;
167 break;
169 if(tpath.IsAdminDir())
171 status = git_wc_status_none;
172 break;
174 TGITCacheResponse itemStatus;
175 SecureZeroMemory(&itemStatus, sizeof(itemStatus));
176 if (m_remoteCacheLink.GetStatusFromRemoteCache(tpath, &itemStatus, true))
178 if (itemStatus.m_bAssumeValid)
179 readonlyoverlay = true;
180 if (itemStatus.m_bSkipWorktree)
181 lockedoverlay = true;
182 status = (git_wc_status_kind)itemStatus.m_status;
185 break;
186 case ShellCache::dll:
187 case ShellCache::dllFull:
189 // Look in our caches for this item
190 const FileStatusCacheEntry * s = m_CachedStatus.GetCachedItem(CTGitPath(pPath));
191 if (s)
193 status = s->status;
194 if (s->assumeValid)
195 readonlyoverlay = true;
196 if (s->skipWorktree)
197 lockedoverlay = true;
199 else
201 // No cached status available
203 // since the dwAttrib param of the IsMemberOf() function does not
204 // have the SFGAO_FOLDER flag set at all (it's 0 for files and folders!)
205 // we have to check if the path is a folder ourselves :(
206 if (PathIsDirectory(pPath))
208 if (g_ShellCache.HasGITAdminDir(pPath, TRUE))
210 if ((!g_ShellCache.IsRecursive()) && (!g_ShellCache.IsFolderOverlay()))
211 status = git_wc_status_normal;
212 else
214 s = m_CachedStatus.GetFullStatus(CTGitPath(pPath), TRUE);
215 status = s->status;
218 else
219 status = git_wc_status_none;
221 else if (CStringUtils::EndsWith(pPath, GitAdminDir::GetAdminDirName()))
222 status = git_wc_status_none;
223 else
225 s = m_CachedStatus.GetFullStatus(CTGitPath(pPath), FALSE);
226 status = s->status;
227 if (s->assumeValid)
228 readonlyoverlay = true;
229 if (s->skipWorktree)
230 lockedoverlay = true;
235 break;
236 default:
237 case ShellCache::none:
239 // no cache means we only show a 'versioned' overlay on folders
240 // with an admin directory
241 if (PathIsDirectory(pPath))
243 if (g_ShellCache.HasGITAdminDir(pPath, TRUE))
244 status = git_wc_status_normal;
245 else
246 status = git_wc_status_none;
248 else
249 status = git_wc_status_none;
251 break;
253 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Status %d for file %s\n", status, pwszPath);
255 g_filepath.clear();
256 g_filepath = pPath;
257 g_filestatus = status;
258 g_readonlyoverlay = readonlyoverlay;
259 g_lockedoverlay = lockedoverlay;
261 //the priority system of the shell doesn't seem to work as expected (or as I expected):
262 //as it seems that if one handler returns S_OK then that handler is used, no matter
263 //if other handlers would return S_OK too (they're never called on my machine!)
264 //So we return S_OK for ONLY ONE handler!
266 switch (status)
268 // note: we can show other overlays if due to lack of enough free overlay
269 // slots some of our overlays aren't loaded. But we assume that
270 // at least the 'normal' overlay is available.
271 // if the 'modified' overlay isn't available, we show the 'normal' overlay,
272 // but in this case the overlays don't really provide anything useful anymore.
273 case git_wc_status_none:
274 return S_FALSE;
275 case git_wc_status_unversioned:
276 if (g_ShellCache.ShowUnversionedOverlay() && g_unversionedovlloaded && (m_State == FileStateUnversionedOverlay))
278 g_filepath.clear();
279 return S_OK;
281 return S_FALSE;
282 case git_wc_status_ignored:
283 if (g_ShellCache.ShowIgnoredOverlay() && g_ignoredovlloaded && (m_State == FileStateIgnoredOverlay))
285 g_filepath.clear();
286 return S_OK;
288 return S_FALSE;
289 case git_wc_status_normal:
290 // skip-worktree aka locked has higher priority than assume-valid
291 if ((lockedoverlay)&&(g_lockedovlloaded))
293 if (m_State == FileStateLockedOverlay)
295 g_filepath.clear();
296 return S_OK;
298 else
299 return S_FALSE;
301 else if ((readonlyoverlay)&&(g_readonlyovlloaded))
303 if (m_State == FileStateReadOnly)
305 g_filepath.clear();
306 return S_OK;
308 else
309 return S_FALSE;
311 else if (m_State == FileStateVersioned)
313 g_filepath.clear();
314 return S_OK;
316 else
317 return S_FALSE;
318 case git_wc_status_deleted:
319 if (g_deletedovlloaded)
321 if (m_State == FileStateDeleted)
323 g_filepath.clear();
324 return S_OK;
326 else
327 return S_FALSE;
329 else
331 // the 'deleted' overlay isn't available (due to lack of enough
332 // overlay slots). So just show the 'modified' overlay instead.
333 if (m_State == FileStateModified)
335 g_filepath.clear();
336 return S_OK;
338 else
339 return S_FALSE;
341 case git_wc_status_modified:
342 if (g_modifiedovlloaded)
344 if (m_State == FileStateModified)
346 g_filepath.clear();
347 return S_OK;
349 else
350 return S_FALSE;
352 else
354 if (m_State == FileStateVersioned)
356 g_filepath.clear();
357 return S_OK;
359 else
360 return S_FALSE;
362 case git_wc_status_added:
363 if (g_addedovlloaded)
365 if (m_State== FileStateAddedOverlay)
367 g_filepath.clear();
368 return S_OK;
370 else
371 return S_FALSE;
373 else
375 // the 'added' overlay isn't available (due to lack of enough
376 // overlay slots). So just show the 'modified' overlay instead.
377 if (m_State == FileStateModified)
379 g_filepath.clear();
380 return S_OK;
382 else
383 return S_FALSE;
385 case git_wc_status_conflicted:
386 if (g_conflictedovlloaded)
388 if (m_State == FileStateConflict)
390 g_filepath.clear();
391 return S_OK;
393 else
394 return S_FALSE;
396 else
398 // the 'conflicted' overlay isn't available (due to lack of enough
399 // overlay slots). So just show the 'modified' overlay instead.
400 if (m_State == FileStateModified)
402 g_filepath.clear();
403 return S_OK;
405 else
406 return S_FALSE;
408 default:
409 return S_FALSE;
410 } // switch (status)
411 //return S_FALSE;