1 /* Add entries to the GNU Emacs Program Manager folder.
2 Copyright (C) 1995, 2001-2021 Free Software Foundation, Inc.
3
4 This file is part of GNU Emacs.
5
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Emacs 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.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
18
19 /****************************************************************************
20 *
21 * Program: addpm (adds emacs to the Windows program manager)
22 *
23 * Usage:
24 * argv[1] = install path for emacs
25 *
26 * argv[2] used to be an optional argument for setting the icon.
27 * But now Emacs has a professional looking icon of its own.
28 * If users really want to change it, they can go into the settings of
29 * the shortcut that is created and do it there.
30 */
31
32 /* Use parts of shell API that were introduced by the merge of IE4
33 into the desktop shell. If Windows 95 or NT4 users do not have IE4
34 installed, then the DDE fallback for creating icons the Windows 3.1
35 progman way will be used instead, but that is prone to lockups
36 caused by other applications not servicing their message queues. */
37
38 #define DEFER_MS_W32_H
39 #include <config.h>
40
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <malloc.h>
44
45 /* MinGW64 barfs if _WIN32_IE is defined to anything below 0x0500. */
46 #ifndef MINGW_W64
47 # ifdef _WIN32_IE
48 # undef _WIN32_IE
49 # endif
50 #define _WIN32_IE 0x0400
51 #endif
52 /* Request C Object macros for COM interfaces. */
53 #define COBJMACROS 1
54
55 #include <windows.h>
56 #include <shlobj.h>
57 #include <ddeml.h>
58
59 #ifndef OLD_PATHS
60 #include "../src/epaths.h"
61 #endif
62
63 HDDEDATA CALLBACK DdeCallback (UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, DWORD_PTR,
64 DWORD_PTR);
65
66 HDDEDATA CALLBACK
DdeCallback(UINT uType,UINT uFmt,HCONV hconv,HSZ hsz1,HSZ hsz2,HDDEDATA hdata,DWORD_PTR dwData1,DWORD_PTR dwData2)67 DdeCallback (UINT uType, UINT uFmt, HCONV hconv,
68 HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
69 DWORD_PTR dwData1, DWORD_PTR dwData2)
70 {
71 return ((HDDEDATA) NULL);
72 }
73
74 #define DdeCommand(str) \
75 DdeClientTransaction ((LPBYTE)str, strlen (str)+1, conversation, (HSZ)NULL, \
76 CF_TEXT, XTYP_EXECUTE, 30000, NULL)
77
78 #define REG_ROOT "SOFTWARE\\GNU\\Emacs"
79 #define REG_APP_PATH \
80 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\emacs.exe"
81
82 static struct entry
83 {
84 const char *name;
85 const char *value;
86 }
87 env_vars[] =
88 {
89 #ifdef OLD_PATHS
90 {"emacs_dir", NULL},
91 {"EMACSLOADPATH", "%emacs_dir%/site-lisp;%emacs_dir%/../site-lisp;%emacs_dir%/lisp"},
92 {"SHELL", "%emacs_dir%/bin/cmdproxy.exe"},
93 {"EMACSDATA", "%emacs_dir%/etc"},
94 {"EMACSPATH", "%emacs_dir%/bin"},
95 /* We no longer set INFOPATH because Info-default-directory-list
96 is then ignored. */
97 /* {"INFOPATH", "%emacs_dir%/info"}, */
98 {"EMACSDOC", "%emacs_dir%/etc"},
99 {"TERM", "cmd"}
100 #else /* !OLD_PATHS */
101 {"emacs_dir", NULL},
102 {"EMACSLOADPATH", PATH_SITELOADSEARCH ";" PATH_LOADSEARCH},
103 {"SHELL", PATH_EXEC "/cmdproxy.exe"},
104 {"EMACSDATA", PATH_DATA},
105 {"EMACSPATH", PATH_EXEC},
106 /* We no longer set INFOPATH because Info-default-directory-list
107 is then ignored. */
108 /* {"INFOPATH", "%emacs_dir%/info"}, */
109 {"EMACSDOC", PATH_DOC},
110 {"TERM", "cmd"}
111 #endif
112 };
113
114 static void
add_registry(const char * path)115 add_registry (const char *path)
116 {
117 HKEY hrootkey = NULL;
118 int i;
119
120 /* Record the location of Emacs to the App Paths key if we have
121 sufficient permissions to do so. This helps Windows find emacs quickly
122 if the user types emacs.exe in the "Run Program" dialog without having
123 emacs on their path. Some applications also use the same registry key
124 to discover the installation directory for programs they are looking for.
125 Multiple installations cannot be handled by this method, but it does not
126 affect the general operation of other installations of Emacs, and we
127 are blindly overwriting the Start Menu entries already.
128 */
129 if (RegCreateKeyEx (HKEY_LOCAL_MACHINE, REG_APP_PATH, 0, NULL,
130 REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
131 &hrootkey, NULL) == ERROR_SUCCESS)
132 {
133 int len;
134 char *emacs_path;
135
136 len = strlen (path) + 15; /* \bin\emacs.exe + terminator. */
137 emacs_path = (char *) alloca (len);
138 sprintf (emacs_path, "%s\\bin\\emacs.exe", path);
139
140 RegSetValueEx (hrootkey, NULL, 0, REG_EXPAND_SZ, emacs_path, len);
141 RegCloseKey (hrootkey);
142 }
143
144 /* Previous versions relied on registry settings, but we do not need
145 them any more. If registry settings are installed from a previous
146 version, replace them to ensure they are the current settings.
147 Otherwise, do nothing. */
148
149 /* Check both the current user and the local machine to see if we
150 have any resources. */
151
152 if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, REG_ROOT, 0,
153 KEY_WRITE | KEY_QUERY_VALUE, &hrootkey) != ERROR_SUCCESS
154 && RegOpenKeyEx (HKEY_CURRENT_USER, REG_ROOT, 0,
155 KEY_WRITE | KEY_QUERY_VALUE, &hrootkey) != ERROR_SUCCESS)
156 return;
157
158 for (i = 0; i < (sizeof (env_vars) / sizeof (env_vars[0])); i++)
159 {
160 const char * value = env_vars[i].value ? env_vars[i].value : path;
161
162 /* Replace only those settings that already exist. */
163 if (RegQueryValueEx (hrootkey, env_vars[i].name, NULL,
164 NULL, NULL, NULL) == ERROR_SUCCESS)
165 RegSetValueEx (hrootkey, env_vars[i].name, 0, REG_EXPAND_SZ,
166 value, lstrlen (value) + 1);
167 }
168
169 RegCloseKey (hrootkey);
170 }
171
172 int
main(int argc,char * argv[])173 main (int argc, char *argv[])
174 {
175 char start_folder[MAX_PATH + 1];
176 int shortcuts_created = 0;
177 int com_available = 1;
178 char modname[MAX_PATH];
179 const char *prog_name;
180 const char *emacs_path;
181 char *p;
182 int quiet = 0;
183 HRESULT result;
184 IShellLinkA *shortcut;
185
186 /* If no args specified, use our location to set emacs_path. */
187
188 if (argc > 1
189 && (argv[1][0] == '/' || argv[1][0] == '-')
190 && argv[1][1] == 'q')
191 {
192 quiet = 1;
193 --argc;
194 ++argv;
195 }
196
197 if (argc > 1)
198 emacs_path = argv[1];
199 else
200 {
201 if (!GetModuleFileName (NULL, modname, MAX_PATH) ||
202 (p = strrchr (modname, '\\')) == NULL)
203 {
204 fprintf (stderr, "fatal error");
205 exit (1);
206 }
207 *p = 0;
208
209 /* Set emacs_path to emacs_dir if we are in "%emacs_dir%\bin". */
210 if ((p = strrchr (modname, '\\')) && stricmp (p, "\\bin") == 0)
211 {
212 *p = 0;
213 emacs_path = modname;
214 }
215 else
216 {
217 fprintf (stderr, "usage: addpm emacs_path\n");
218 exit (1);
219 }
220
221 /* Tell user what we are going to do. */
222 if (!quiet)
223 {
224 int result;
225
226 const char install_msg[] = "Install Emacs at %s?\n";
227 char msg[ MAX_PATH + sizeof (install_msg) ];
228 sprintf (msg, install_msg, emacs_path);
229 result = MessageBox (NULL, msg, "Install Emacs",
230 MB_OKCANCEL | MB_ICONQUESTION);
231 if (result != IDOK)
232 {
233 fprintf (stderr, "Install canceled\n");
234 exit (1);
235 }
236 }
237 }
238
239 add_registry (emacs_path);
240 prog_name = "runemacs.exe";
241
242 /* Try to install globally. */
243
244 if (!SUCCEEDED (CoInitialize (NULL))
245 || !SUCCEEDED (CoCreateInstance (&CLSID_ShellLink, NULL,
246 CLSCTX_INPROC_SERVER, &IID_IShellLinkA,
247 (void **) &shortcut)))
248 {
249 com_available = 0;
250 }
251
252 if (com_available
253 && SHGetSpecialFolderPath (NULL, start_folder, CSIDL_COMMON_PROGRAMS, 0))
254 {
255 if (strlen (start_folder) < (MAX_PATH - 20))
256 {
257 strcat (start_folder, "\\Gnu Emacs");
258 if (CreateDirectory (start_folder, NULL)
259 || GetLastError () == ERROR_ALREADY_EXISTS)
260 {
261 char full_emacs_path[MAX_PATH + 1];
262 IPersistFile *lnk;
263 strcat (start_folder, "\\Emacs.lnk");
264 sprintf (full_emacs_path, "%s\\bin\\%s", emacs_path, prog_name);
265 IShellLinkA_SetPath (shortcut, full_emacs_path);
266 IShellLinkA_SetDescription (shortcut, "GNU Emacs");
267 result = IShellLinkA_QueryInterface (shortcut, &IID_IPersistFile,
268 (void **) &lnk);
269 if (SUCCEEDED (result))
270 {
271 wchar_t unicode_path[MAX_PATH];
272 MultiByteToWideChar (CP_ACP, 0, start_folder, -1,
273 unicode_path, MAX_PATH);
274 if (SUCCEEDED (IPersistFile_Save (lnk, unicode_path, TRUE)))
275 shortcuts_created = 1;
276 IPersistFile_Release (lnk);
277 }
278 }
279 }
280 }
281
282 if (!shortcuts_created && com_available
283 && SHGetSpecialFolderPath (NULL, start_folder, CSIDL_PROGRAMS, 0))
284 {
285 /* Ensure there is enough room for "...\GNU Emacs\Emacs.lnk". */
286 if (strlen (start_folder) < (MAX_PATH - 20))
287 {
288 strcat (start_folder, "\\Gnu Emacs");
289 if (CreateDirectory (start_folder, NULL)
290 || GetLastError () == ERROR_ALREADY_EXISTS)
291 {
292 char full_emacs_path[MAX_PATH + 1];
293 IPersistFile *lnk;
294 strcat (start_folder, "\\Emacs.lnk");
295 sprintf (full_emacs_path, "%s\\bin\\%s", emacs_path, prog_name);
296 IShellLinkA_SetPath (shortcut, full_emacs_path);
297 IShellLinkA_SetDescription (shortcut, "GNU Emacs");
298 result = IShellLinkA_QueryInterface (shortcut, &IID_IPersistFile,
299 (void **) &lnk);
300 if (SUCCEEDED (result))
301 {
302 wchar_t unicode_path[MAX_PATH];
303 MultiByteToWideChar (CP_ACP, 0, start_folder, -1,
304 unicode_path, MAX_PATH);
305 if (SUCCEEDED (IPersistFile_Save (lnk, unicode_path, TRUE)))
306 shortcuts_created = 1;
307 IPersistFile_Release (lnk);
308
309 }
310 }
311 }
312 }
313
314 if (com_available)
315 IShellLinkA_Release (shortcut);
316
317 /* Need to call uninitialize, even if ComInitialize failed. */
318 CoUninitialize ();
319
320 /* Fallback on old DDE method if the above failed. */
321 if (!shortcuts_created)
322 {
323 DWORD dde = 0;
324 HCONV conversation;
325 HSZ progman;
326 char add_item[MAX_PATH*2 + 100];
327
328 DdeInitialize (&dde, (PFNCALLBACK) DdeCallback, APPCMD_CLIENTONLY, 0);
329 progman = DdeCreateStringHandle (dde, "PROGMAN", CP_WINANSI);
330 conversation = DdeConnect (dde, progman, progman, NULL);
331 if (conversation)
332 {
333 DdeCommand ("[CreateGroup (\"Gnu Emacs\")]");
334 DdeCommand ("[ReplaceItem (Emacs)]");
335 sprintf (add_item, "[AddItem (\"%s\\bin\\%s\", Emacs)]",
336 emacs_path, prog_name);
337 DdeCommand (add_item);
338
339 DdeDisconnect (conversation);
340 }
341
342 DdeFreeStringHandle (dde, progman);
343 DdeUninitialize (dde);
344 }
345
346 return 0;
347 }
348