Shut up linker
[git-cheetah.git] / dll.c
blobaab59884ace70c7572b42474561bf84b257fbe99
1 #include <shlobj.h>
2 #include <stdio.h>
3 #include "dll.h"
4 #include "menuengine.h"
5 #include "ext.h"
6 #include "factory.h"
7 #include "systeminfo.h"
8 #include "registry.h"
11 * The following is just the necessary infrastructure for having a .dll
12 * which can be registered as a COM object.
14 static HINSTANCE hInst;
16 HRESULT PASCAL DllGetClassObject(REFCLSID obj_guid, REFIID factory_guid,
17 void **factory_handle)
19 if (IsEqualCLSID(obj_guid, &CLSID_git_shell_ext) ||
20 IsEqualCLSID(obj_guid, &CLSID_git_menu))
21 return class_factory_query_interface(&factory,
22 factory_guid, factory_handle);
24 *factory_handle = 0;
25 return CLASS_E_CLASSNOTAVAILABLE;
28 HRESULT PASCAL DllCanUnloadNow(void)
30 return (object_count || lock_count) ? S_FALSE : S_OK;
33 BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
35 hInst = instance;
37 if (reason == DLL_PROCESS_ATTACH) {
38 object_count = lock_count = 0;
39 DisableThreadLibraryCalls(instance);
42 return 1;
45 /* replaces a substring pattern with a string replacement within a string
46 the replacement occurs in-place, hence string must be large enough to
47 hold the result
49 the function does not handle recursive replacements, e.g.
50 strreplace ("foo", "bar", "another bar");
52 always returns *string
54 static char *strreplace(char *string, const size_t size,
55 const char *pattern, const char *replacement)
57 size_t len = strlen(string);
58 const size_t pattern_len = strlen(pattern);
59 const size_t replace_len = strlen(replacement);
61 char *found = strstr(string, pattern);
63 while (found) {
64 /* if the new len is greater than size, bail out */
65 if (len + replace_len - pattern_len >= size)
66 return string;
68 if (pattern_len != replace_len)
69 memmove(found + replace_len,
70 found + pattern_len,
71 len - (found - string) - pattern_len + 1);
72 memcpy(found, replacement, replace_len);
73 len += replace_len - pattern_len;
75 found = strstr(string, pattern);
78 return string;
82 * The following is the data for our minimal regedit engine,
83 * required for registration/unregistration of the extension
85 #define CLASS_CHEETAH CLASSES_ROOT "CLSID\\@@CLSID@@"
86 #define CONTEXTMENUHANDLER "shellex\\ContextMenuHandlers\\@@PROGRAM_NAME@@"
88 static const char *get_module_filename() {
89 static char module_filename[MAX_PATH] = { '\0' };
91 if (!*module_filename) {
92 DWORD module_size;
94 module_size = GetModuleFileName(hInst,
95 module_filename, MAX_PATH);
96 if (0 == module_size)
97 return NULL;
100 return module_filename;
103 /* as per "How to: Convert Between System::Guid and _GUID" */
104 static const char *get_class_id()
106 static char class_id[MAX_REGISTRY_PATH] = { '\0' };
108 if (!*class_id) {
109 GUID guid = CLSID_git_shell_ext;
110 sprintf(class_id,
111 "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
112 guid.Data1, guid.Data2, guid.Data3,
113 guid.Data4[0], guid.Data4[1], guid.Data4[2],
114 guid.Data4[3], guid.Data4[4], guid.Data4[5],
115 guid.Data4[6], guid.Data4[7]);
118 return class_id;
122 * Tries to find msysGit in the following order:
123 * .. and ../.. (relative to the module)
124 * %PATH%
125 * as qgit (via InstallLocation of Git)
126 SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation
128 static const reg_value registry_info[] = {
129 { CURRENT_WINDOWS APPROVED_EXT, "@@CLSID@@", "@@PROGRAM_NAME@@" },
130 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
131 NULL, NULL },
132 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
133 NULL,"@@PROGRAM_NAME@@" },
134 { CLASS_CHEETAH, NULL, NULL },
135 { CLASS_CHEETAH, NULL, "@@PROGRAM_NAME@@" },
136 { CLASS_CHEETAH "\\InProcServer32", NULL, NULL },
137 { CLASS_CHEETAH "\\InProcServer32", NULL, "@@PROGRAM_PATH@@"},
138 { CLASS_CHEETAH "\\InProcServer32", "ThreadingModel", "Apartment" },
139 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, NULL },
140 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
141 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, NULL },
142 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
143 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
144 NULL, NULL },
145 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
146 NULL, "@@CLSID@@" },
147 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, NULL },
148 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@"},
149 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, NULL },
150 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
151 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
152 NULL, NULL },
153 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
154 NULL, "@@CLSID@@" },
155 { GIT_CHEETAH_REG_PATH, NULL, NULL },
156 { GIT_CHEETAH_REG_PATH,
157 GIT_CHEETAH_REG_PATHTOMSYS, "@@MSYSGIT_PATH@@" },
158 { NULL, NULL, NULL }
161 static const reg_value debug_info[] = {
162 { CURRENT_WINDOWS "Explorer", "DesktopProcess", "1" },
163 { CURRENT_WINDOWS "Explorer\\AlwaysUnloadDll", NULL, NULL },
164 { CURRENT_WINDOWS "Explorer\\Advanced", "SeparateProcess", "1" },
165 { NULL, NULL, NULL }
168 static char msysgit[MAX_PATH] = { '\0' };
170 static BOOL find_msysgit_in_path()
172 char *file; /* file part of the path to git.exe */
173 DWORD dwFound; /* length of path to git.exe */
175 dwFound = SearchPath(NULL, "git.exe", NULL, MAX_PATH, msysgit, &file);
176 /* if git is not in the PATH or its path is too long */
177 if (0 == dwFound ||
178 dwFound > MAX_PATH)
179 return FALSE;
182 * git.exe is in "\bin\" from what we really need
183 * the minimal case we can handle is c:\bin\git.exe
184 * otherwise declare failure
186 if (file < msysgit + 7)
187 return FALSE;
188 if (strnicmp(file - 5, "\\bin\\", 5))
189 return FALSE;
190 file[-5] = '\0';
192 return TRUE;
195 static BOOL find_msysgit_relative(const char *path)
197 char *c;
199 strcpy(msysgit, get_module_filename());
200 c = strrchr(msysgit, '\\');
201 c[1] = '\0';
202 strcat(msysgit, path);
203 strcat(msysgit, "\\bin\\git.exe");
204 if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(msysgit)) {
205 msysgit[0] = '\0'; /* restore the original result */
206 return FALSE;
209 c[1] = '\0';
210 strcat(msysgit, path);
211 return TRUE;
214 static BOOL find_msysgit_uninstall(HKEY root)
216 HKEY key;
217 HRESULT result;
218 DWORD valuelen = MAX_PATH;
220 result = RegOpenKeyEx(root,
221 CURRENT_WINDOWS "\\Uninstall\\Git_is1",
222 0, KEY_READ, &key);
223 if (ERROR_SUCCESS != result)
224 return FALSE;
226 result = RegQueryValue(key, "InstallLocation",
227 (LPBYTE)msysgit, &valuelen);
228 return ERROR_SUCCESS == result;
231 static HKEY setup_root;
233 static const char *find_msysgit()
235 if ('\0' == msysgit[0]) {
236 if (find_msysgit_relative(".."))
237 return msysgit;
239 if (find_msysgit_relative("..\\.."))
240 return msysgit;
242 if (find_msysgit_in_path())
243 return msysgit;
245 if (setup_root)
246 find_msysgit_uninstall(setup_root);
249 return msysgit;
253 * required by registry.c
254 * supports @@PROGRAM_NAME@@, @@PROGRAM_PATH@@, @@CLSID@@ patterns
256 char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH])
258 if (NULL == src)
259 return NULL;
261 strcpy(dst, src);
262 strreplace(dst, MAX_REGISTRY_PATH,
263 "@@PROGRAM_NAME@@", program_name);
264 strreplace(dst, MAX_REGISTRY_PATH,
265 "@@PROGRAM_PATH@@", get_module_filename());
266 strreplace(dst, MAX_REGISTRY_PATH,
267 "@@CLSID@@", get_class_id());
268 strreplace(dst, MAX_REGISTRY_PATH,
269 "@@MSYSGIT_PATH@@", find_msysgit());
271 return dst;
274 HRESULT PASCAL DllRegisterServer(void)
276 setup_root = HKEY_CURRENT_USER;
277 return create_reg_entries (setup_root, registry_info);
280 HRESULT PASCAL DllUnregisterServer(void)
282 setup_root = HKEY_CURRENT_USER;
283 return delete_reg_entries(setup_root, registry_info);
286 /* provide means to create/delete keys:
287 - described in Debugging with The Shell;
288 - machine-wide registration and debugging info
290 possible combinations of regsvr32 command line options:
291 -n (absent) (present)
293 (absent) user reg (invalid)
294 debug user reg+debug user debug
295 machine user+machine reg machine reg
296 machinedebug user+machine reg+debug machine reg+debug
298 Obviously missing option is "machine debug". To accomplish:
299 - execute "regsvr32 -n -i:machinedebug" and then
300 - regsvr32 -u -n -i:machine
302 HRESULT PASCAL DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
304 BOOL bDebug = NULL != wcsstr(pszCmdLine, L"debug");
305 HRESULT result = ERROR_SUCCESS;
307 setup_root = wcsstr(pszCmdLine, L"machine") ?
308 HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
310 if (bInstall) {
311 if (bDebug)
312 result = create_reg_entries(setup_root, debug_info);
314 /* for user-only registration, use DllRegister */
315 if (ERROR_SUCCESS == result &&
316 HKEY_LOCAL_MACHINE == setup_root)
317 result = create_reg_entries(setup_root,
318 registry_info);
319 } else { /* uninstall */
320 if (bDebug)
321 result = delete_reg_entries(setup_root, debug_info);
323 /* for user-only unregistration, use DllUnregister */
324 if (ERROR_SUCCESS == result &&
325 HKEY_LOCAL_MACHINE == setup_root)
326 result = delete_reg_entries(setup_root,
327 registry_info);
330 return result;