Fix find_msysgit_in_path() when PATH is in cmd folder rather than bin
[git-cheetah.git] / explorer / dll.c
blobbbfae11ba21c1b9d6cfb8fd0d597ed2af18f7968
2 #include "../common/cache.h"
4 #include <shlobj.h>
5 #include <stdio.h>
6 #include "dll.h"
7 #include "../common/menuengine.h"
8 #include "ext.h"
9 #include "factory.h"
10 #include "../common/systeminfo.h"
11 #include "registry.h"
13 const char *program_name = "Git-Cheetah";
14 const char *program_version = "Git-Cheetah.Application.1";
15 const char *program_id = "Git-Cheetah.Application";
18 * The following is just the necessary infrastructure for having a .dll
19 * which can be registered as a COM object.
21 HINSTANCE hInst;
23 HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID obj_guid, REFIID factory_guid,
24 void **factory_handle)
26 if (IsEqualCLSID(obj_guid, &CLSID_git_shell_ext) ||
27 IsEqualCLSID(obj_guid, &CLSID_git_menu))
28 return class_factory_query_interface(&factory,
29 factory_guid, factory_handle);
31 *factory_handle = 0;
32 return CLASS_E_CLASSNOTAVAILABLE;
35 HRESULT STDAPICALLTYPE DllCanUnloadNow(void)
37 return (object_count || lock_count) ? S_FALSE : S_OK;
40 BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
42 hInst = instance;
44 if (reason == DLL_PROCESS_ATTACH) {
45 object_count = lock_count = 0;
46 DisableThreadLibraryCalls(instance);
49 return 1;
52 /* replaces a substring pattern with a string replacement within a string
53 the replacement occurs in-place, hence string must be large enough to
54 hold the result
56 the function does not handle recursive replacements, e.g.
57 strreplace ("foo", "bar", "another bar");
59 always returns *string
61 static char *strreplace(char *string, const size_t size,
62 const char *pattern, const char *replacement)
64 size_t len = strlen(string);
65 const size_t pattern_len = strlen(pattern);
66 const size_t replace_len = strlen(replacement);
68 char *found = strstr(string, pattern);
70 while (found) {
71 /* if the new len is greater than size, bail out */
72 if (len + replace_len - pattern_len >= size)
73 return string;
75 if (pattern_len != replace_len)
76 memmove(found + replace_len,
77 found + pattern_len,
78 len - (found - string) - pattern_len + 1);
79 memcpy(found, replacement, replace_len);
80 len += replace_len - pattern_len;
82 found = strstr(string, pattern);
85 return string;
89 * The following is the data for our minimal regedit engine,
90 * required for registration/unregistration of the extension
92 #define CLASS_CHEETAH CLASSES_ROOT "CLSID\\@@CLSID@@"
93 #define CONTEXTMENUHANDLER "shellex\\ContextMenuHandlers\\@@PROGRAM_NAME@@"
94 #define COLUMNHANDLER "shellex\\ColumnHandlers"
96 static const char *get_module_filename() {
97 static char module_filename[MAX_PATH] = { '\0' };
99 if (!*module_filename) {
100 DWORD module_size;
102 module_size = GetModuleFileName(hInst,
103 module_filename, MAX_PATH);
104 if (0 == module_size)
105 return NULL;
108 return module_filename;
111 /* as per "How to: Convert Between System::Guid and _GUID" */
112 static const char *get_class_id()
114 static char class_id[MAX_REGISTRY_PATH] = { '\0' };
116 if (!*class_id) {
117 GUID guid = CLSID_git_shell_ext;
118 sprintf(class_id,
119 "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
120 guid.Data1, guid.Data2, guid.Data3,
121 guid.Data4[0], guid.Data4[1], guid.Data4[2],
122 guid.Data4[3], guid.Data4[4], guid.Data4[5],
123 guid.Data4[6], guid.Data4[7]);
126 return class_id;
130 * Tries to find msysGit in the following order:
131 * .. and ../.. (relative to the module)
132 * %PATH%
133 * as qgit (via InstallLocation of Git)
134 SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation
136 static const reg_value registry_info[] = {
137 { CURRENT_WINDOWS APPROVED_EXT, "@@CLSID@@", "@@PROGRAM_NAME@@" },
138 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
139 NULL, NULL },
140 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
141 NULL,"@@PROGRAM_NAME@@" },
142 { CLASS_CHEETAH, NULL, NULL },
143 { CLASS_CHEETAH, NULL, "@@PROGRAM_NAME@@" },
144 { CLASS_CHEETAH "\\InProcServer32", NULL, NULL },
145 { CLASS_CHEETAH "\\InProcServer32", NULL, "@@PROGRAM_PATH@@"},
146 { CLASS_CHEETAH "\\InProcServer32", "ThreadingModel", "Apartment" },
147 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, NULL },
148 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
149 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, NULL },
150 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
151 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
152 NULL, NULL },
153 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
154 NULL, "@@CLSID@@" },
155 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, NULL },
156 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@"},
157 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, NULL },
158 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
159 { CLASSES_ROOT "Folder\\" COLUMNHANDLER "\\@@CLSID@@", NULL, NULL },
160 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
161 NULL, NULL },
162 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
163 NULL, "@@CLSID@@" },
164 { GIT_CHEETAH_REG_PATH, NULL, NULL },
165 { GIT_CHEETAH_REG_PATH,
166 GIT_CHEETAH_REG_PATHTOMSYS, "@@MSYSGIT_PATH@@" },
167 { NULL, NULL, NULL }
170 static const reg_value debug_info[] = {
171 { CURRENT_WINDOWS "Explorer", "DesktopProcess", "1" },
172 { CURRENT_WINDOWS "Explorer\\AlwaysUnloadDll", NULL, NULL },
173 { CURRENT_WINDOWS "Explorer\\Advanced", "SeparateProcess", "1" },
174 { NULL, NULL, NULL }
177 static char msysgit[MAX_PATH] = { '\0' };
179 static BOOL find_msysgit_in_path()
181 char *file; /* file part of the path to git.exe */
182 DWORD dwFound; /* length of path to git.exe */
184 dwFound = SearchPath(NULL, "git.exe", NULL, MAX_PATH, msysgit, &file);
185 /* if git is not in the PATH or its path is too long */
186 if (0 == dwFound ||
187 dwFound > MAX_PATH)
188 return FALSE;
191 * git.exe is in "\bin\" from what we really need
192 * the minimal case we can handle is c:\bin\git.exe
193 * otherwise declare failure. We additionally check
194 * for Git for Windows where it is placed in ..\Git\cmd\
196 if (file < msysgit + 7)
197 return FALSE;
198 if (strnicmp(file - 5, "\\bin\\", 5) && strnicmp(file - 5, "\\cmd\\", 5))
199 return FALSE;
200 file[-5] = '\0';
202 return TRUE;
205 static BOOL find_msysgit_relative(const char *path)
207 char *c;
209 strcpy(msysgit, get_module_filename());
210 c = strrchr(msysgit, '\\');
211 c[1] = '\0';
212 strcat(msysgit, path);
213 strcat(msysgit, "\\bin\\git.exe");
214 if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(msysgit)) {
215 msysgit[0] = '\0'; /* restore the original result */
216 return FALSE;
219 c[1] = '\0';
220 strcat(msysgit, path);
221 return TRUE;
224 static BOOL find_msysgit_uninstall(HKEY root)
226 HKEY key;
227 HRESULT result;
228 DWORD valuelen = MAX_PATH;
230 result = RegOpenKeyEx(root,
231 CURRENT_WINDOWS "\\Uninstall\\Git_is1",
232 0, KEY_READ, &key);
233 if (ERROR_SUCCESS != result)
234 return FALSE;
236 result = RegQueryValue(key, "InstallLocation",
237 (LPBYTE)msysgit, &valuelen);
238 return ERROR_SUCCESS == result;
241 static HKEY setup_root;
243 static const char *find_msysgit()
245 if ('\0' == msysgit[0]) {
246 if (find_msysgit_relative(".."))
247 return msysgit;
249 if (find_msysgit_relative("..\\.."))
250 return msysgit;
252 if (find_msysgit_in_path())
253 return msysgit;
255 if (setup_root)
256 find_msysgit_uninstall(setup_root);
259 return msysgit;
263 * required by registry.c
264 * supports @@PROGRAM_NAME@@, @@PROGRAM_PATH@@, @@CLSID@@ patterns
266 char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH])
268 if (NULL == src)
269 return NULL;
271 strcpy(dst, src);
272 strreplace(dst, MAX_REGISTRY_PATH,
273 "@@PROGRAM_NAME@@", program_name);
274 strreplace(dst, MAX_REGISTRY_PATH,
275 "@@PROGRAM_PATH@@", get_module_filename());
276 strreplace(dst, MAX_REGISTRY_PATH,
277 "@@CLSID@@", get_class_id());
278 strreplace(dst, MAX_REGISTRY_PATH,
279 "@@MSYSGIT_PATH@@", find_msysgit());
281 return dst;
284 HRESULT PASCAL DllRegisterServer(void)
286 HRESULT retval;
287 setup_root = HKEY_CURRENT_USER;
288 retval = create_reg_entries (setup_root, registry_info);
289 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST,NULL,NULL);
290 return retval;
293 HRESULT PASCAL DllUnregisterServer(void)
295 HRESULT retval;
296 setup_root = HKEY_CURRENT_USER;
297 retval = delete_reg_entries(setup_root, registry_info);
298 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST,NULL,NULL);
299 return retval;
302 /* provide means to create/delete keys:
303 - described in Debugging with The Shell;
304 - machine-wide registration and debugging info
306 possible combinations of regsvr32 command line options:
307 -n (absent) (present)
309 (absent) user reg (invalid)
310 debug user reg+debug user debug
311 machine user+machine reg machine reg
312 machinedebug user+machine reg+debug machine reg+debug
314 Obviously missing option is "machine debug". To accomplish:
315 - execute "regsvr32 -n -i:machinedebug" and then
316 - regsvr32 -u -n -i:machine
318 HRESULT PASCAL DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
320 BOOL bDebug = NULL != wcsstr(pszCmdLine, L"debug");
321 HRESULT result = ERROR_SUCCESS;
323 setup_root = wcsstr(pszCmdLine, L"machine") ?
324 HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
326 if (bInstall) {
327 if (bDebug)
328 result = create_reg_entries(setup_root, debug_info);
330 /* for user-only registration, use DllRegister */
331 if (ERROR_SUCCESS == result &&
332 HKEY_LOCAL_MACHINE == setup_root)
333 result = create_reg_entries(setup_root,
334 registry_info);
335 } else { /* uninstall */
336 if (bDebug)
337 result = delete_reg_entries(setup_root, debug_info);
339 /* for user-only unregistration, use DllUnregister */
340 if (ERROR_SUCCESS == result &&
341 HKEY_LOCAL_MACHINE == setup_root)
342 result = delete_reg_entries(setup_root,
343 registry_info);
345 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST,NULL,NULL);
346 return result;