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