Implement self-registration of the extension
[git-cheetah/kirill.git] / dll.c
blobb694e8b264fc116573bba050a0c909731d0492ed
1 #include <shlobj.h>
2 #include <stdio.h>
3 #include "dll.h"
4 #include "ext.h"
5 #include "factory.h"
6 #include "systeminfo.h"
7 #include "registry.h"
9 /*
10 * The following is just the necessary infrastructure for having a .dll
11 * which can be registered as a COM object.
13 static HINSTANCE hInst;
15 HRESULT PASCAL DllGetClassObject(REFCLSID obj_guid, REFIID factory_guid,
16 void **factory_handle)
18 if (IsEqualCLSID(obj_guid, &CLSID_git_shell_ext) ||
19 IsEqualCLSID(obj_guid, &CLSID_git_menu))
20 return class_factory_query_interface(&factory,
21 factory_guid, factory_handle);
23 *factory_handle = 0;
24 return CLASS_E_CLASSNOTAVAILABLE;
27 HRESULT PASCAL DllCanUnloadNow(void)
29 return (object_count || lock_count) ? S_FALSE : S_OK;
32 BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
34 hInst = instance;
36 if (reason == DLL_PROCESS_ATTACH) {
37 object_count = lock_count = 0;
38 DisableThreadLibraryCalls(instance);
41 return 1;
44 /* replaces a substring pattern with a string replacement within a string
45 the replacement occurs in-place, hence string must be large enough to
46 hold the result
48 the function does not handle recursive replacements, e.g.
49 strreplace ("foo", "bar", "another bar");
51 always returns *string
53 static char *strreplace(char *string, const size_t size,
54 const char *pattern, const char *replacement)
56 size_t len = strlen(string);
57 const size_t pattern_len = strlen(pattern);
58 const size_t replace_len = strlen(replacement);
60 char *found = strstr(string, pattern);
62 while (found) {
63 /* if the new len is greater than size, bail out */
64 if (len + replace_len - pattern_len >= size)
65 return string;
67 if (pattern_len != replace_len)
68 memmove(found + replace_len,
69 found + pattern_len,
70 len - (found - string) - pattern_len + 1);
71 memcpy(found, replacement, replace_len);
72 len += replace_len - pattern_len;
74 found = strstr(string, pattern);
77 return string;
81 * The following is the data for our minimal regedit engine,
82 * required for registration/unregistration of the extension
84 #define CLASS_CHEETAH CLASSES_ROOT "CLSID\\@@CLSID@@"
85 #define CONTEXTMENUHANDLER "shellex\\ContextMenuHandlers\\@@PROGRAM_NAME@@"
87 static const char *get_module_filename() {
88 static char module_filename[MAX_PATH] = { '\0' };
90 if (!*module_filename) {
91 DWORD module_size;
93 module_size = GetModuleFileName(hInst,
94 module_filename, MAX_PATH);
95 if (0 == module_size)
96 return NULL;
99 return module_filename;
102 /* as per "How to: Convert Between System::Guid and _GUID" */
103 static const char *get_class_id()
105 static char class_id[MAX_REGISTRY_PATH] = { '\0' };
107 if (!*class_id) {
108 GUID guid = CLSID_git_shell_ext;
109 sprintf(class_id,
110 "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
111 guid.Data1, guid.Data2, guid.Data3,
112 guid.Data4[0], guid.Data4[1], guid.Data4[2],
113 guid.Data4[3], guid.Data4[4], guid.Data4[5],
114 guid.Data4[6], guid.Data4[7]);
117 return class_id;
121 * Tries to find msysGit in the following order:
122 * .. and ../.. (relative to the module)
123 * %PATH%
124 * as qgit (via InstallLocation of Git)
125 SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation
127 static const reg_value registry_info[] = {
128 { CURRENT_WINDOWS APPROVED_EXT, "@@CLSID@@", "@@PROGRAM_NAME@@" },
129 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
130 NULL, NULL },
131 { CURRENT_WINDOWS APPROVED_EXT "\\@@CLSID@@",
132 NULL,"@@PROGRAM_NAME@@" },
133 { CLASS_CHEETAH, NULL, NULL },
134 { CLASS_CHEETAH, NULL, "@@PROGRAM_NAME@@" },
135 { CLASS_CHEETAH "\\InProcServer32", NULL, NULL },
136 { CLASS_CHEETAH "\\InProcServer32", NULL, "@@PROGRAM_PATH@@"},
137 { CLASS_CHEETAH "\\InProcServer32", "ThreadingModel", "Apartment" },
138 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, NULL },
139 { CLASSES_ROOT "*\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
140 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, NULL },
141 { CLASSES_ROOT "Directory\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
142 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
143 NULL, NULL },
144 { CLASSES_ROOT "Directory\\Background\\" CONTEXTMENUHANDLER,
145 NULL, "@@CLSID@@" },
146 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, NULL },
147 { CLASSES_ROOT "Drive\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@"},
148 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, NULL },
149 { CLASSES_ROOT "Folder\\" CONTEXTMENUHANDLER, NULL, "@@CLSID@@" },
150 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
151 NULL, NULL },
152 { CLASSES_ROOT "InternetShortcut\\" CONTEXTMENUHANDLER,
153 NULL, "@@CLSID@@" },
154 { GIT_CHEETAH_REG_PATH, NULL, NULL },
155 { GIT_CHEETAH_REG_PATH,
156 GIT_CHEETAH_REG_PATHTOMSYS, "@@MSYSGIT_PATH@@" },
157 { NULL, NULL, NULL }
160 static const reg_value debug_info[] = {
161 { CURRENT_WINDOWS "Explorer", "DesktopProcess", "1" },
162 { CURRENT_WINDOWS "Explorer\\AlwaysUnloadDll", NULL, NULL },
163 { CURRENT_WINDOWS "Explorer\\Advanced", "SeparateProcess", "1" },
164 { NULL, NULL, NULL }
167 static char msysgit[MAX_PATH] = { '\0' };
169 static BOOL find_msysgit_in_path()
171 char *file; /* file part of the path to git.exe */
172 DWORD dwFound; /* length of path to git.exe */
174 dwFound = SearchPath(NULL, "git.exe", NULL, MAX_PATH, msysgit, &file);
175 /* if git is not in the PATH or its path is too long */
176 if (0 == dwFound ||
177 dwFound > MAX_PATH)
178 return FALSE;
181 * git.exe is in "\bin\" from what we really need
182 * the minimal case we can handle is c:\bin\git.exe
183 * otherwise declare failure
185 if (file < msysgit + 7)
186 return FALSE;
187 if (strnicmp(file - 5, "\\bin\\", 5))
188 return FALSE;
189 file[-5] = '\0';
191 return TRUE;
194 static BOOL find_msysgit_relative(const char *path)
196 char *c;
198 strcpy(msysgit, get_module_filename());
199 c = strrchr(msysgit, '\\');
200 c[1] = '\0';
201 strcat(msysgit, path);
202 strcat(msysgit, "\\bin\\git.exe");
203 if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(msysgit)) {
204 msysgit[0] = '\0'; /* restore the original result */
205 return FALSE;
208 c[1] = '\0';
209 strcat(msysgit, path);
210 return TRUE;
213 static BOOL find_msysgit_uninstall(HKEY root)
215 HKEY key;
216 HRESULT result;
217 DWORD valuelen = MAX_PATH;
219 result = RegOpenKeyEx(root,
220 CURRENT_WINDOWS "\\Uninstall\\Git_is1",
221 0, KEY_READ, &key);
222 if (ERROR_SUCCESS != result)
223 return FALSE;
225 result = RegQueryValue(key, "InstallLocation",
226 (LPBYTE)msysgit, &valuelen);
227 return ERROR_SUCCESS == result;
230 static HKEY setup_root;
232 static const char *find_msysgit()
234 if ('\0' == msysgit[0]) {
235 if (find_msysgit_relative(".."))
236 return msysgit;
238 if (find_msysgit_relative("..\\.."))
239 return msysgit;
241 if (find_msysgit_in_path())
242 return msysgit;
244 if (setup_root)
245 find_msysgit_uninstall(setup_root);
248 return msysgit;
252 * required by registry.c
253 * supports @@PROGRAM_NAME@@, @@PROGRAM_PATH@@, @@CLSID@@ patterns
255 char *get_registry_path(const char *src, char dst[MAX_REGISTRY_PATH])
257 if (NULL == src)
258 return NULL;
260 strcpy(dst, src);
261 strreplace(dst, MAX_REGISTRY_PATH,
262 "@@PROGRAM_NAME@@", program_name);
263 strreplace(dst, MAX_REGISTRY_PATH,
264 "@@PROGRAM_PATH@@", get_module_filename());
265 strreplace(dst, MAX_REGISTRY_PATH,
266 "@@CLSID@@", get_class_id());
267 strreplace(dst, MAX_REGISTRY_PATH,
268 "@@MSYSGIT_PATH@@", find_msysgit());
270 return dst;
273 HRESULT PASCAL DllRegisterServer(void)
275 setup_root = HKEY_CURRENT_USER;
276 return create_reg_entries (setup_root, registry_info);
279 HRESULT PASCAL DllUnregisterServer(void)
281 setup_root = HKEY_CURRENT_USER;
282 return delete_reg_entries(setup_root, registry_info);
285 /* provide means to create/delete keys:
286 - described in Debugging with The Shell;
287 - machine-wide registration and debugging info
289 possible combinations of regsvr32 command line options:
290 -n (absent) (present)
292 (absent) user reg (invalid)
293 debug user reg+debug user debug
294 machine user+machine reg machine reg
295 machinedebug user+machine reg+debug machine reg+debug
297 Obviously missing option is "machine debug". To accomplish:
298 - execute "regsvr32 -n -i:machinedebug" and then
299 - regsvr32 -u -n -i:machine
301 HRESULT PASCAL DllInstall(BOOL bInstall, LPCWSTR pszCmdLine)
303 BOOL bDebug = NULL != wcsstr(pszCmdLine, L"debug");
304 HRESULT result = ERROR_SUCCESS;
306 setup_root = wcsstr(pszCmdLine, L"machine") ?
307 HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
309 if (bInstall) {
310 if (bDebug)
311 result = create_reg_entries(setup_root, debug_info);
313 /* for user-only registration, use DllRegister */
314 if (ERROR_SUCCESS == result &&
315 HKEY_LOCAL_MACHINE == setup_root)
316 result = create_reg_entries(setup_root,
317 registry_info);
318 } else { /* uninstall */
319 if (bDebug)
320 result = delete_reg_entries(setup_root, debug_info);
322 /* for user-only unregistration, use DllUnregister */
323 if (ERROR_SUCCESS == result &&
324 HKEY_LOCAL_MACHINE == setup_root)
325 result = delete_reg_entries(setup_root,
326 registry_info);
329 return result;