1 /* mkshortcut.c -- create a Windows shortcut
3 * Copyright (c) 2002 Joshua Daniel Franklin
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * See the COPYING file for full license information.
21 * 1: user error (syntax error)
22 * 2: system error (out of memory, etc.)
23 * 3: windows error (interface failed)
25 * Compile with: gcc -o prog.exe mkshortcut.c -lpopt -lole32 -luuid
26 * (You'd need to uncomment the moved to common.h lines.)
35 #define NOCOMATTRIBUTE
39 /* moved to common.h */
44 #include <sys/cygwin.h>
46 static const char versionID
[] = PACKAGE_VERSION
;
47 static const char revID
[] =
49 static const char copyrightID
[] =
50 "Copyright (c) 2002\nJoshua Daniel Franklin. All rights reserved.\nLicensed under GPL v2.0\n";
52 typedef struct optvals_s
70 static int mkshortcut (optvals opts
);
71 static void printTopDescription (FILE * f
, char *name
);
72 static void printBottomDescription (FILE * f
, char *name
);
73 static const char *getVersion ();
74 static void usage (FILE * f
, char *name
);
75 static void help (FILE * f
, char *name
);
76 static void version (FILE * f
, char *name
);
77 static void license (FILE * f
, char *name
);
79 static char *program_name
;
80 static poptContext optCon
;
83 main (int argc
, const char **argv
)
94 struct poptOption helpOptionsTable
[] = {
95 {"help", 'h', POPT_ARG_NONE
, NULL
, '?',
96 "Show this help message", NULL
},
97 {"usage", '\0', POPT_ARG_NONE
, NULL
, 'u',
98 "Display brief usage message", NULL
},
99 {"version", 'v', POPT_ARG_NONE
, NULL
, 'v',
100 "Display version information", NULL
},
101 {"license", '\0', POPT_ARG_NONE
, NULL
, 'l',
102 "Display licensing information", NULL
},
103 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
106 struct poptOption generalOptionsTable
[] = {
107 {"arguments", 'a', POPT_ARG_STRING
, NULL
, 'a',
108 "Use arguments ARGS", "ARGS"},
109 {"desc", 'd', POPT_ARG_STRING
, NULL
, 'd',
110 "Text for description/tooltip (defaults to POSIX path of TARGET)",
112 {"icon", 'i', POPT_ARG_STRING
, NULL
, 'i',
113 "Icon file for link to use", "ICONFILE"},
114 {"iconoffset", 'j', POPT_ARG_INT
, &(opts
.offset
), 'j',
115 "Offset of icon in icon file (default is 0)", NULL
},
116 {"name", 'n', POPT_ARG_STRING
, NULL
, 'n',
117 "Name for link (defaults to TARGET)", "NAME"},
118 {"show", 's', POPT_ARG_STRING
, NULL
, 's',
119 "Window to show: normal, minimized, maximized", "norm|min|max"},
120 {"workingdir", 'w', POPT_ARG_STRING
, NULL
, 'w',
121 "Set working directory (defaults to directory path of TARGET)", "PATH"},
122 {"allusers", 'A', POPT_ARG_VAL
, &(opts
.allusers_flag
), 1,
123 "Use 'All Users' instead of current user for -D,-P", NULL
},
124 {"desktop", 'D', POPT_ARG_VAL
, &(opts
.desktop_flag
), 1,
125 "Create link relative to 'Desktop' directory", NULL
},
126 {"smprograms", 'P', POPT_ARG_VAL
, &(opts
.smprograms_flag
), 1,
127 "Create link relative to Start Menu 'Programs' directory", NULL
},
128 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
131 struct poptOption opt
[] = {
132 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, generalOptionsTable
, 0,
133 "General options", NULL
},
134 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, helpOptionsTable
, 0,
135 "Help options", NULL
},
136 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
139 tmp_str
= strrchr (argv
[0], '/');
142 tmp_str
= strrchr (argv
[0], '\\');
152 if ((program_name
= strdup (tmp_str
)) == NULL
)
154 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
158 icon_offset_flag
= 0;
163 opts
.windows_flag
= 0;
164 opts
.allusers_flag
= 0;
165 opts
.desktop_flag
= 0;
166 opts
.smprograms_flag
= 0;
167 opts
.show_flag
= SW_SHOWNORMAL
;
168 opts
.target_arg
= NULL
;
169 opts
.argument_arg
= NULL
;
170 opts
.name_arg
= NULL
;
171 opts
.desc_arg
= NULL
;
172 opts
.dir_name_arg
= NULL
;
173 opts
.icon_name_arg
= NULL
;
176 optCon
= poptGetContext (NULL
, argc
, argv
, opt
, 0);
177 poptSetOtherOptionHelp (optCon
, "[OPTION]* TARGET");
178 while ((rc
= poptGetNextOpt (optCon
)) > 0)
183 help (stdout
, program_name
);
186 usage (stdout
, program_name
);
189 version (stdout
, program_name
);
192 license (stdout
, program_name
);
195 if (arg
= poptGetOptArg (optCon
))
197 if ((opts
.desc_arg
= strdup (arg
)) == NULL
)
199 fprintf (stderr
, "%s: memory allocation error\n",
208 if (arg
= poptGetOptArg (optCon
))
210 opts
.icon_name_arg
= (char *) cygwin_create_path(
211 CCP_POSIX_TO_WIN_A
, arg
);
212 if (opts
.icon_name_arg
== NULL
)
214 fprintf (stderr
, "%s: error converting posix path to win32 (%s)\n",
215 program_name
, strerror(errno
));
222 icon_offset_flag
= 1;
225 if (arg
= poptGetOptArg (optCon
))
227 if ((opts
.name_arg
= strdup (arg
)) == NULL
)
229 fprintf (stderr
, "%s: memory allocation error\n",
237 if (arg
= poptGetOptArg (optCon
))
239 if (strcmp (arg
, "min") == 0)
241 opts
.show_flag
= SW_SHOWMINNOACTIVE
;
243 else if (strcmp (arg
, "max") == 0)
245 opts
.show_flag
= SW_SHOWMAXIMIZED
;
247 else if (strcmp (arg
, "norm") == 0)
249 opts
.show_flag
= SW_SHOWNORMAL
;
253 fprintf (stderr
, "%s: %s not valid for show window\n",
261 if (arg
= poptGetOptArg (optCon
))
263 if ((opts
.dir_name_arg
= strdup (arg
)) == NULL
)
265 fprintf (stderr
, "%s: memory allocation error\n",
273 if (arg
= poptGetOptArg (optCon
))
275 if ((opts
.argument_arg
= strdup (arg
)) == NULL
)
277 fprintf (stderr
, "%s: memory allocation error\n",
286 // case 'P' all handled by popt itself
290 if (icon_offset_flag
& !opts
.icon_flag
)
293 "%s: --iconoffset|-j only valid in conjuction with --icon|-i\n",
295 usage (stderr
, program_name
);
300 if (opts
.smprograms_flag
&& opts
.desktop_flag
)
303 "%s: --smprograms|-P not valid in conjuction with --desktop|-D\n",
305 usage (stderr
, program_name
);
312 fprintf (stderr
, "%s: bad argument %s: %s\n",
313 program_name
, poptBadOption (optCon
, POPT_BADOPTION_NOALIAS
),
319 rest
= poptGetArgs (optCon
);
323 if ((opts
.target_arg
= strdup (*rest
)) == NULL
)
325 fprintf (stderr
, "%s: memory allocation error\n", program_name
);
332 fprintf (stderr
, "%s: Too many arguments: ", program_name
);
334 fprintf (stderr
, "%s ", *rest
++);
335 fprintf (stderr
, "\n");
336 usage (stderr
, program_name
);
341 // THE MEAT GOES HERE
342 ec
= mkshortcut (opts
);
347 fprintf (stderr
, "%s: TARGET not specified\n", program_name
);
348 usage (stderr
, program_name
);
353 poptFreeContext (optCon
);
356 free (opts
.target_arg
);
360 free (opts
.name_arg
);
364 free (opts
.desc_arg
);
366 if (opts
.dir_name_arg
)
368 free (opts
.dir_name_arg
);
370 if (opts
.argument_arg
)
372 free (opts
.argument_arg
);
374 if (opts
.icon_name_arg
)
376 free (opts
.icon_name_arg
);
383 xstrndup (const char *string
, size_t n
)
385 char *s
= strndup (string
, n
);
388 fprintf (stderr
, "%s: out of memory\n", program_name
);
395 xstrncat (char **dest
, const char *add
, size_t n
)
397 size_t len
= strlen (*dest
) + n
+ 1;
398 char *s
= (char *) realloc (*dest
, len
* sizeof (char));
401 fprintf (stderr
, "%s: out of memory\n", program_name
);
405 return strncat (*dest
, add
, n
);
409 mkshortcut (optvals opts
)
411 char* link_name
= NULL
;
412 char* exe_name
= NULL
;
413 char* dir_name
= NULL
;
415 char *buf_str
, *tmp_str
, *base_str
;
418 /* For OLE interface */
421 IShellLink
*shell_link
;
422 IPersistFile
*persist_file
;
423 WCHAR widepath
[MAX_PATH
];
424 char link_path
[MAX_PATH
];
426 /* If there's a colon in the TARGET, it should be a URL */
427 if (strchr (opts
.target_arg
, ':') != NULL
)
429 /* Nope, somebody's trying a W32 path */
430 if (opts
.target_arg
[1] == ':')
432 fprintf (stderr
, "%s: all paths must be in POSIX format\n",
434 usage (stderr
, program_name
);
437 exe_name
= xstrndup (opts
.target_arg
, strlen(opts
.target_arg
));
438 dir_name
= xstrndup ("", 0); /* No working dir for URL */
440 /* Convert TARGET to win32 path */
443 buf_str
= xstrndup (opts
.target_arg
, strlen(opts
.target_arg
));
444 exe_name
= (char *) cygwin_create_path (CCP_POSIX_TO_WIN_A
,
448 fprintf (stderr
, "%s: error converting posix path to win32 (%s)\n",
449 program_name
, strerror(errno
));
454 /* Get a working dir from 'w' option */
455 if (opts
.dir_name_arg
!= NULL
)
457 if (strchr (opts
.dir_name_arg
, ':') != NULL
)
461 fprintf (stderr
, "%s: all paths must be in POSIX format\n",
463 usage (stderr
, program_name
);
466 dir_name
= (char *) cygwin_create_path (CCP_POSIX_TO_WIN_A
,
470 fprintf (stderr
, "%s: error converting posix path to win32 (%s)\n",
471 program_name
, strerror(errno
));
477 /* Get a working dir from the exepath */
480 tmp_str
= strrchr (exe_name
, '\\');
481 tmp
= strlen (exe_name
) - strlen (tmp_str
);
482 dir_name
= xstrndup (exe_name
, tmp
);
483 dir_name
[tmp
] = '\0';
485 if (buf_str
) free (buf_str
);
488 /* Generate a name for the link if not given */
489 if (opts
.name_arg
== NULL
)
491 /* Strip trailing /'s if any */
492 buf_str
= xstrndup (opts
.target_arg
, strlen(opts
.target_arg
));
495 tmp
= strlen (buf_str
) - 1;
496 while (strrchr (buf_str
, '/') == (buf_str
+ tmp
))
505 tmp_str
= buf_str
+ 1;
508 link_name
= xstrndup (tmp_str
, strlen (tmp_str
));
511 /* User specified a name, so check it and convert */
514 if (opts
.desktop_flag
|| opts
.smprograms_flag
)
516 /* Cannot have absolute path relative to Desktop/SM Programs */
517 if (opts
.name_arg
[0] == '/')
520 "%s: absolute pathnames not allowed with -D/-P\n",
522 usage (stderr
, program_name
);
526 /* Sigh. Another W32 path */
527 if (strchr (opts
.name_arg
, ':') != NULL
)
529 fprintf (stderr
, "%s: all paths must be in POSIX format\n",
531 usage (stderr
, program_name
);
534 link_name
= (char *) cygwin_create_path (
535 CCP_POSIX_TO_WIN_A
| CCP_RELATIVE
, opts
.name_arg
);
538 fprintf (stderr
, "%s: error converting posix path to win32 (%s)\n",
539 program_name
, strerror(errno
));
544 /* Add suffix to link name if necessary */
545 if (strlen (link_name
) > 4)
547 tmp
= strlen (link_name
) - 4;
548 if (strncmp (link_name
+ tmp
, ".lnk", 4) != 0)
549 xstrncat (&link_name
, ".lnk", 4);
552 xstrncat (&link_name
, ".lnk", 4);
554 /* Prepend relative path if necessary */
555 if (opts
.desktop_flag
)
557 char local_buf
[MAX_PATH
];
558 buf_str
= xstrndup (link_name
, strlen (link_name
));
560 if (!opts
.allusers_flag
)
561 SHGetSpecialFolderLocation (NULL
, CSIDL_DESKTOPDIRECTORY
, &id
);
563 SHGetSpecialFolderLocation (NULL
, CSIDL_COMMON_DESKTOPDIRECTORY
, &id
);
564 SHGetPathFromIDList (id
, local_buf
);
565 /* Make sure Win95 without "All Users" has output */
566 if (strlen (local_buf
) == 0)
568 SHGetSpecialFolderLocation (NULL
, CSIDL_DESKTOPDIRECTORY
, &id
);
569 SHGetPathFromIDList (id
, local_buf
);
572 link_name
= xstrndup (local_buf
, strlen (local_buf
));
573 xstrncat (&link_name
, "\\", 1);
574 xstrncat (&link_name
, buf_str
, strlen (buf_str
));
578 if (opts
.smprograms_flag
)
580 char local_buf
[MAX_PATH
];
581 buf_str
= xstrndup (link_name
, strlen (link_name
));
583 if (!opts
.allusers_flag
)
584 SHGetSpecialFolderLocation (NULL
, CSIDL_PROGRAMS
, &id
);
586 SHGetSpecialFolderLocation (NULL
, CSIDL_COMMON_PROGRAMS
, &id
);
587 SHGetPathFromIDList (id
, local_buf
);
588 /* Make sure Win95 without "All Users" has output */
589 if (strlen (local_buf
) == 0)
591 SHGetSpecialFolderLocation (NULL
, CSIDL_PROGRAMS
, &id
);
592 SHGetPathFromIDList (id
, local_buf
);
595 link_name
= xstrndup (local_buf
, strlen (local_buf
));
596 xstrncat (&link_name
, "\\", 1);
597 xstrncat (&link_name
, buf_str
, strlen (buf_str
));
601 /* After Windows 7, saving link to relative path fails; work around that */
602 hres
= GetFullPathName (link_name
, sizeof (link_path
), link_path
, 0);
605 fprintf (stderr
, "%s: Could not qualify link name\n", program_name
);
609 link_name
= xstrndup (link_path
, strlen (link_path
));
611 /* Setup description text */
612 if (opts
.desc_arg
!= NULL
)
614 desc
= xstrndup (opts
.desc_arg
, strlen (opts
.desc_arg
));
618 /* Put the POSIX path in the "Description", just to be nice */
619 desc
= (char *) cygwin_create_path (CCP_WIN_A_TO_POSIX
, exe_name
);
622 fprintf (stderr
, "%s: error converting win32 path to posix (%s)\n",
623 program_name
, strerror(errno
));
628 /* Beginning of Windows interface */
629 hres
= OleInitialize (NULL
);
630 if (hres
!= S_FALSE
&& hres
!= S_OK
)
632 fprintf (stderr
, "%s: Could not initialize OLE interface\n",
642 CoCreateInstance (&CLSID_ShellLink
, NULL
, CLSCTX_INPROC_SERVER
,
643 &IID_IShellLink
, (void **) &shell_link
);
644 if (SUCCEEDED (hres
))
647 shell_link
->lpVtbl
->QueryInterface (shell_link
, &IID_IPersistFile
,
648 (void **) &persist_file
);
649 if (SUCCEEDED (hres
))
651 shell_link
->lpVtbl
->SetPath (shell_link
, exe_name
);
652 shell_link
->lpVtbl
->SetDescription (shell_link
, desc
);
653 shell_link
->lpVtbl
->SetWorkingDirectory (shell_link
, dir_name
);
654 if (opts
.argument_arg
)
655 shell_link
->lpVtbl
->SetArguments (shell_link
, opts
.argument_arg
);
657 shell_link
->lpVtbl
->SetIconLocation (shell_link
,
660 if (opts
.show_flag
!= SW_SHOWNORMAL
)
661 shell_link
->lpVtbl
->SetShowCmd (shell_link
, opts
.show_flag
);
664 /* Make link name Unicode-compliant */
666 MultiByteToWideChar (CP_ACP
, 0, link_name
, -1, widepath
,
668 if (!SUCCEEDED (hres
))
670 fprintf (stderr
, "%s: Unicode translation failed%d\n",
678 hres
= persist_file
->lpVtbl
->Save (persist_file
, widepath
, TRUE
);
679 if (!SUCCEEDED (hres
))
682 "%s: Saving \"%s\" failed; does the target directory exist?\n",
683 program_name
, link_name
);
690 persist_file
->lpVtbl
->Release (persist_file
);
691 shell_link
->lpVtbl
->Release (shell_link
);
693 /* If we are creating shortcut for all users, ensure it is readable by all users */
694 if (opts
.allusers_flag
)
696 char *posixpath
= (char *) cygwin_create_path (
697 CCP_WIN_W_TO_POSIX
| CCP_ABSOLUTE
, widepath
);
698 if (posixpath
&& *posixpath
)
701 if (stat(posixpath
, &statbuf
))
704 "%s: stat \"%s\" failed\n",
705 program_name
, posixpath
);
707 else if (chmod(posixpath
, statbuf
.st_mode
|S_IRUSR
|S_IRGRP
|S_IROTH
))
710 "%s: chmod \"%s\" failed\n",
711 program_name
, posixpath
);
724 fprintf (stderr
, "%s: QueryInterface failed\n", program_name
);
734 fprintf (stderr
, "%s: CoCreateInstance failed\n", program_name
);
750 printTopDescription (FILE * f
, char *name
)
753 fprintf (f
, "%s is part of cygutils version %s\n", name
, getVersion ());
754 fprintf (f
, " create a Windows shortcut\n\n");
757 printBottomDescription (FILE * f
, char *name
)
760 "\nNOTE: All filename arguments must be in unix (POSIX) format\n");
764 printLicense (FILE * f
, char *name
)
767 "This program is free software: you can redistribute it and/or modify\n"
768 "it under the terms of the GNU General Public License as published by\n"
769 "the Free Software Foundation, either version 3 of the License, or\n"
770 "(at your option) any later version.\n\n"
771 "This program is distributed in the hope that it will be useful,\n"
772 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
773 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
774 "GNU General Public License for more details.\n\n"
775 "You should have received a copy of the GNU General Public License\n"
776 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n"
777 "See the COPYING file for full license information.\n");
781 usage (FILE * f
, char *name
)
783 poptPrintUsage (optCon
, f
, 0);
787 help (FILE * f
, char *name
)
789 printTopDescription (f
, name
);
790 poptPrintHelp (optCon
, f
, 0);
791 printBottomDescription (f
, name
);
795 version (FILE * f
, char *name
)
797 printTopDescription (f
, name
);
798 fprintf (f
, copyrightID
);
802 license (FILE * f
, char *name
)
804 printTopDescription (f
, name
);
805 printLicense (f
, name
);