2 * cygstart - Let Windows start a program, or open a file or URL
4 * (c) 2002 Michael Schaap <cygstart(at)mscha.org>
6 * This program 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
9 * (at your option) any later version.
11 * This program 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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * See the COPYING file for full license information.
31 #include <sys/cygwin.h>
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "cygstart"
36 #define AUTHORS "Michael Schaap"
38 /* Predefined actions */
39 #define ACTION_OPEN "open"
40 #define ACTION_EXPLORE "explore"
41 #define ACTION_EDIT "edit"
42 #define ACTION_FIND "find"
43 #define ACTION_PRINT "print"
45 /* MSDN reference URL */
46 #define MSDN_URL "http://msdn.microsoft.com/en-us/library/bb762153%28VS.85%29.aspx"
48 static const char versionID
[] = PACKAGE_VERSION
;
49 static const char revID
[] =
51 static const char copyrightID
[] =
52 "Copyright (c) 2002,...\n"
53 "Michael Schaap. All rights reserved.\n" "Licensed under GPL v2.0\n";
55 /* The name this program was run with. */
56 static char *program_name
;
57 static poptContext optCon
;
59 typedef enum StartFlags
66 /* ShellExecuteW returns an HINSTANCE -- which is a pointer whose
67 * size differs on 32bit and 64bit platforms -- but it is really
68 * an integer type, NOT a pointer. We need to cast it to an integer
69 * type, but...which one? This ugliness avoids compile warnings.
72 #define INTEGER_CAST long long
74 #define INTEGER_CAST int
77 static int cygStart (const char *aPath
, const wchar_t * action
,
78 const wchar_t * args
, const char *workDir
,
79 int show
, StartFlags startFlags
);
80 static int winStart (const wchar_t * aPath
, const wchar_t * action
,
81 const wchar_t * args
, const wchar_t * workDir
,
82 int show
, StartFlags startFlags
);
83 static char *startError (INTEGER_CAST err
);
84 static const char *getVersion (void);
85 static void printTopDescription (FILE * f
, char *name
);
86 static void printBottomDescription (FILE * f
, char *name
);
87 static void usage (FILE * f
, char *name
);
88 static void help (FILE * f
, char *name
);
89 static void version (FILE * f
, char *name
);
90 static void license (FILE * f
, char *name
);
93 mbstowcs_noerr (wchar_t * wcs
, const char *mbs
, size_t n
)
95 size_t wcsLen
= mbstowcs (wcs
, mbs
, n
);
96 if (wcsLen
== (size_t) - 1)
98 fprintf (stderr
, "%s: multibyte to wide string conversion error\n",
106 mbstowcs_dup (const char *mbs
)
108 size_t len
= mbstowcs (NULL
, mbs
, 0);
109 if (len
== (size_t) - 1)
111 fprintf (stderr
, "%s: multibyte to wide string conversion error\n",
115 wchar_t *wcs
= (wchar_t *) malloc (sizeof (wchar_t) * (len
+ 1));
116 size_t wcsLen
= mbstowcs_noerr (wcs
, mbs
, len
);
122 main (int argc
, const char **argv
)
128 wchar_t *action
= NULL
;
132 wchar_t *args
= NULL
;
133 char *workDir
= NULL
;
134 int show
= SW_SHOWNORMAL
;
135 StartFlags startFlags
= SF_NONE
;
137 setlocale (LC_ALL
, "");
140 struct poptOption actionOptionsTable
[] = {
141 {"action", 'a', POPT_ARG_STRING
, NULL
, 'a',
142 "Use specified action instead of default", NULL
},
143 {"open", 'o', POPT_ARG_NONE
, NULL
, 'o',
144 "Short for: --action open", NULL
},
145 {"explore", 'x', POPT_ARG_NONE
, NULL
, 'x',
146 "Short for: --action explore", NULL
},
147 {"edit", 'e', POPT_ARG_NONE
, NULL
, 'e',
148 "Short for: --action edit", NULL
},
149 {"find", 'f', POPT_ARG_NONE
, NULL
, 'f',
150 "Short for: --action find", NULL
},
151 {"print", 'p', POPT_ARG_NONE
, NULL
, 'p',
152 "Short for: --action print", NULL
},
153 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
156 /* Directory options */
157 struct poptOption directoryOptionsTable
[] = {
158 {"directory", 'd', POPT_ARG_STRING
, NULL
, 'd',
159 "Set working directory", NULL
},
160 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
164 struct poptOption showOptionsTable
[] = {
165 {"hide", '\0', POPT_ARG_NONE
, NULL
, 'H',
166 "Hides the window and activates another window", NULL
},
167 {"maximize", '\0', POPT_ARG_NONE
, NULL
, 'M',
168 "Maximizes the specified window", NULL
},
169 {"minimize", '\0', POPT_ARG_NONE
, NULL
, 'N',
170 "Minimizes the specified window and activates the next top-level "
171 "window in the z-order", NULL
},
172 {"restore", '\0', POPT_ARG_NONE
, NULL
, 'R',
173 "Activates and displays the window. If the window is minimized or "
174 "maximized, Windows restores it to its original size and position. "
175 "An application should specify this flag when restoring a minimized "
177 {"show", '\0', POPT_ARG_NONE
, NULL
, 'S',
178 "Activates the window and displays it in its current size and "
180 {"showmaximized", '\0', POPT_ARG_NONE
, NULL
, 'X',
181 "Activates the window and displays it as a maximized window", NULL
},
182 {"showminimized", '\0', POPT_ARG_NONE
, NULL
, 'Y',
183 "Activates the window and displays it as a minimized window", NULL
},
184 {"showminnoactive", '\0', POPT_ARG_NONE
, NULL
, 'Z',
185 "Displays the window as a minimized window. The active window "
186 "remains active", NULL
},
187 {"showna", '\0', POPT_ARG_NONE
, NULL
, 'A',
188 "Displays the window in its current state. The active window "
189 "remains active", NULL
},
190 {"shownoactivate", '\0', POPT_ARG_NONE
, NULL
, 'V',
191 "Displays a window in its most recent size and position. The "
192 "active window remains active", NULL
},
193 {"shownormal", '\0', POPT_ARG_NONE
, NULL
, 'O',
194 "Activates and displays a window. If the window is minimized or "
195 "maximized, Windows restores it to its original size and position. "
196 "An application should specify this flag when displaying the window "
197 "for the first time", NULL
},
198 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
201 /* Startup options */
202 struct poptOption startupOptionsTable
[] = {
203 {"wait", 'w', POPT_ARG_NONE
, NULL
, 'w',
204 "Waits until the started application terminates before exiting.",
206 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
209 /* Troubleshooting options */
210 struct poptOption troubleOptionsTable
[] = {
211 {"verbose", 'v', POPT_ARG_NONE
, NULL
, 'E',
212 "Show the actual ShellExecute call made", NULL
},
213 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
217 struct poptOption helpOptionsTable
[] = {
218 {"help", '?', POPT_ARG_NONE
, NULL
, '?',
219 "Show this help message", NULL
},
220 {"usage", '\0', POPT_ARG_NONE
, NULL
, 'u',
221 "Display brief usage message", NULL
},
222 {"version", '\0', POPT_ARG_NONE
, NULL
, 'v',
223 "Display version information", NULL
},
224 {"license", '\0', POPT_ARG_NONE
, NULL
, 'l',
225 "Display licensing information", NULL
},
226 {"reference", '\0', POPT_ARG_NONE
, NULL
, 'r',
227 "Open MSDN reference for ShellExecute", NULL
},
228 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
231 struct poptOption opt
[] = {
232 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, actionOptionsTable
, 0,
233 "Action options", NULL
},
234 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, directoryOptionsTable
, 0,
235 "Directory options", NULL
},
236 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, showOptionsTable
, 0,
237 "Show options", NULL
},
238 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, startupOptionsTable
, 0,
239 "Startup options", NULL
},
240 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, troubleOptionsTable
, 0,
241 "Troubleshooting options", NULL
},
242 {NULL
, '\0', POPT_ARG_INCLUDE_TABLE
, helpOptionsTable
, 0,
243 "Help options", NULL
},
244 {NULL
, '\0', 0, NULL
, 0, NULL
, NULL
}
247 if ((program_name
= strdup (argv
[0])) == NULL
)
249 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
254 optCon
= poptGetContext (NULL
, argc
, argv
, opt
, POPT_CONTEXT_POSIXMEHARDER
);
255 poptSetOtherOptionHelp (optCon
, "[OPTION]... FILE [ARGUMENTS]");
256 while ((rc
= poptGetNextOpt (optCon
)) > 0)
262 help (stdout
, program_name
);
263 poptFreeContext (optCon
);
271 usage (stdout
, program_name
);
272 poptFreeContext (optCon
);
280 version (stdout
, program_name
);
281 poptFreeContext (optCon
);
289 license (stdout
, program_name
);
290 poptFreeContext (optCon
);
298 cygStart (MSDN_URL
, NULL
, NULL
, NULL
, SW_NORMAL
, startFlags
);
299 poptFreeContext (optCon
);
309 if (arg
= poptGetOptArg (optCon
))
311 if ((action
= mbstowcs_dup (arg
)) == NULL
)
313 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
319 if ((action
= mbstowcs_dup (ACTION_OPEN
)) == NULL
)
321 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
326 if ((action
= mbstowcs_dup (ACTION_EXPLORE
)) == NULL
)
328 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
333 if ((action
= mbstowcs_dup (ACTION_EDIT
)) == NULL
)
335 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
340 if ((action
= mbstowcs_dup (ACTION_FIND
)) == NULL
)
342 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
347 if ((action
= mbstowcs_dup (ACTION_PRINT
)) == NULL
)
349 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
354 /* Directory options */
356 if (arg
= poptGetOptArg (optCon
))
358 if ((workDir
= strdup (arg
)) == NULL
)
360 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
383 show
= SW_SHOWMAXIMIZED
;
386 show
= SW_SHOWMINIMIZED
;
389 show
= SW_SHOWMINNOACTIVE
;
395 show
= SW_SHOWNOACTIVATE
;
398 show
= SW_SHOWNORMAL
;
401 /* Startup options */
403 startFlags
|= SF_WAIT
;
406 /* Troubleshooting options */
408 startFlags
|= SF_VERBOSE
;
414 fprintf (stderr
, "%s: bad argument %s: %s\n",
415 program_name
, poptBadOption (optCon
, POPT_BADOPTION_NOALIAS
),
417 poptFreeContext (optCon
);
425 rest
= poptGetArgs (optCon
);
427 /* Determine file (or program, or URL) to start */
430 if ((file
= strdup (*rest
)) == NULL
)
432 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
439 usage (stdout
, program_name
);
443 /* Retrieve any arguments */
447 argLength
= strlen (*tmp
);
448 while (tmp
++ && *tmp
)
450 argLength
+= 1 + strlen (*tmp
);
452 if ((args
= (wchar_t *) malloc (sizeof (wchar_t) * (argLength
+ 1)))
455 fprintf (stderr
, "%s: memory allocation error\n", argv
[0]);
458 size_t argOffset
= mbstowcs_noerr (args
, *rest
, argLength
);
459 while (rest
++ && *rest
)
461 args
[argOffset
++] = L
' ';
462 size_t len
= mbstowcs_noerr (args
+ argOffset
, *rest
,
463 argLength
- argOffset
);
466 args
[argOffset
] = L
'\0';
470 ret
= cygStart (file
, action
, args
, workDir
, show
, startFlags
);
472 poptFreeContext (optCon
);
486 /* ShellExecute*W is TOO SLOW when there is '\\?\' */
487 static const wchar_t *
488 skipLocalUNCPart (const wchar_t * path
)
491 if ((wcslen (path
) < MAX_PATH
+ 4)
492 && (!wcsncmp (path
, L
"\\\\?\\", 4)) && (path
[5] == L
':'))
498 #if defined(__CYGWIN__)
500 cygstart_posix_to_win_w (const char *posix_mbs_path
, wchar_t **w32_wcs_path
)
503 ssize_t len
= cygwin_conv_path (CCP_POSIX_TO_WIN_W
|CCP_RELATIVE
,
504 posix_mbs_path
, NULL
, 0);
508 "%s: error converting path `%s' from cygwin to native format: %s\n",
509 program_name
, posix_mbs_path
, strerror (errno
));
514 *w32_wcs_path
= (wchar_t *) malloc ((len
+ 1) * sizeof (wchar_t));
517 fprintf (stderr
, "%s: memory allocation error\n", program_name
);
522 if (cygwin_conv_path (CCP_POSIX_TO_WIN_W
|CCP_RELATIVE
,
523 posix_mbs_path
, *w32_wcs_path
,
524 (len
+ 1) * sizeof (wchar_t)) < 0)
527 "%s: error converting path `%s' from cygwin to format: %s\n",
528 program_name
, posix_mbs_path
, strerror (errno
));
536 free (*w32_wcs_path
);
540 #endif /* __CYGWIN__ */
543 cygstart_mbs_to_wcs (const char *mbs_path
, wchar_t **wcs_path
)
546 size_t len
= mbstowcs (NULL
, mbs_path
, 0);
547 if (len
== (size_t) - 1)
549 fprintf (stderr
, "%s: error converting path `%s' to unicode: %s\n",
550 program_name
, mbs_path
, strerror (errno
));
555 *wcs_path
= (wchar_t *) malloc ((len
+ 1) * sizeof (wchar_t));
558 fprintf (stderr
, "%s: memory allocation error\n", program_name
);
563 if (mbstowcs (*wcs_path
, mbs_path
, (len
+ 1) * sizeof (wchar_t)) ==
566 fprintf (stderr
, "%s: error converting path `%s' to unicode: %s\n",
567 program_name
, mbs_path
, strerror (errno
));
572 (*wcs_path
)[len
] = L
'\0';
582 /* Start a program, or open a file or URL, using Cygwin POSIX paths */
584 cygStart (const char *aPath
, const wchar_t * action
,
585 const wchar_t * args
, const char *workDir
,
586 int show
, StartFlags startFlags
)
588 wchar_t *winPath
= NULL
;
589 wchar_t *winDir
= NULL
;
590 const wchar_t *pWinPath
= NULL
;
591 const wchar_t *pWinDir
= NULL
;
594 if (strncmp (aPath
, "file://", 7) == 0)
597 /* Convert file path from POSIX to Windows, unless it looks like a URL */
598 if (!strstr (aPath
, "://") && strncmp (aPath
, "mailto:", 7) != 0)
600 rc
= cygstart_posix_to_win_w (aPath
, &winPath
);
601 if (rc
) goto cleanup
;
602 pWinPath
= skipLocalUNCPart (winPath
);
605 #endif /* __CYGWIN__ */
607 rc
= cygstart_mbs_to_wcs (aPath
, &winPath
);
608 if (rc
) goto cleanup
;
612 /* Convert working directory, if any, from POSIX to Windows */
616 rc
= cygstart_posix_to_win_w (workDir
, &winDir
);
618 rc
= cygstart_mbs_to_wcs (workDir
, &winDir
);
620 if (rc
) goto cleanup
;
621 pWinDir
= skipLocalUNCPart (winDir
);
622 rc
= winStart (pWinPath
, action
, args
, pWinDir
, show
, startFlags
);
626 rc
= winStart (pWinPath
, action
, args
, NULL
, show
, startFlags
);
638 static void printLastError (FILE * file
);
640 /* Start a program, or open a file or URL, using Windows paths */
642 winStart (const wchar_t * aPath
, const wchar_t * action
,
643 const wchar_t * args
, const wchar_t * workDir
,
644 int show
, StartFlags startFlags
)
647 /* Need to sync the Windows environment */
648 cygwin_internal (CW_SYNC_WINENV
);
651 if (startFlags
& SF_VERBOSE
)
654 (L
"ShellExecute(NULL, \"%ls\", \"%ls\", \"%ls\", \"%ls\", %d)\n",
655 action
, aPath
, args
, workDir
, show
);
658 if (!(startFlags
& SF_WAIT
))
661 (INTEGER_CAST
) ShellExecuteW (NULL
, action
, aPath
, args
, workDir
, show
);
669 fwprintf (stderr
, L
"Unable to start '%ls': %s\n", aPath
,
676 SHELLEXECUTEINFOW sei
;
678 memset (&sei
, 0, sizeof (sei
));
679 sei
.cbSize
= sizeof (sei
);
682 sei
.lpParameters
= args
;
683 sei
.lpDirectory
= workDir
;
685 sei
.fMask
|= SEE_MASK_NOCLOSEPROCESS
| SEE_MASK_FLAG_NO_UI
;
687 if (!ShellExecuteExW (&sei
))
689 if (((INTEGER_CAST
)sei
.hInstApp
) < 32)
691 fwprintf (stderr
, L
"Unable to start '%ls': %s\n", aPath
,
692 startError ((INTEGER_CAST
) sei
.hInstApp
));
697 fwprintf (stderr
, L
"Unable to start '%ls': ", aPath
);
698 printLastError (stderr
);
699 fprintf (stderr
, "\n");
707 WaitForSingleObject (sei
.hProcess
, INFINITE
);
708 if (!GetExitCodeProcess (sei
.hProcess
, &code
))
712 CloseHandle (sei
.hProcess
);
720 /* Print a correctly-localized error message for GetLastError() to the given
723 printLastError (FILE * file
)
727 if (!FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
, 0, GetLastError (), 0, (LPSTR
) & buf
, 0, // min size
730 // avoid recursion getting message - possible but infinite-prone
731 fprintf (file
, "Couldn't retrieve error message");
740 /* Return an error message, given a ShellExecute return code */
742 startError (INTEGER_CAST err
)
747 return "The operating system is out of memory or resources.";
748 case ERROR_FILE_NOT_FOUND
:
749 return "The specified file was not found.";
750 case ERROR_PATH_NOT_FOUND
:
751 return "The specified path was not found.";
752 case ERROR_BAD_FORMAT
:
753 return "The .exe file is invalid (non-Win32 .exe or error in "
755 case SE_ERR_ACCESSDENIED
:
756 return "The operating system denied access to the specified file.";
757 case SE_ERR_ASSOCINCOMPLETE
:
758 return "The file name association is incomplete or invalid.";
760 return "The DDE transaction could not be completed because "
761 "other DDE transactions were being processed.";
763 return "The DDE transaction failed.";
764 case SE_ERR_DDETIMEOUT
:
765 return "The DDE transaction could not be completed because the "
766 "request timed out.";
767 case SE_ERR_DLLNOTFOUND
:
768 return "The specified dynamic-link library was not found.";
770 return "There is no application associated with the given file "
773 return "There was not enough memory to complete the operation.";
775 return "A sharing violation occurred.";
777 return "An unknown error occurred.";
788 printTopDescription (FILE * f
, char *name
)
790 fprintf (f
, "%s is part of cygutils version %s\n", name
, getVersion ());
791 fprintf (f
, "%s was originally authored by %s\n", name
, AUTHORS
);
792 fprintf (f
, "\nLet Windows start a program or open a file or URL.\n\n");
796 printBottomDescription (FILE * f
, char *name
)
799 fprintf (f
, "With thanks to MSDN: <%s>\n\n", MSDN_URL
);
800 fprintf (f
, "Please report any bugs to <cygwin(at)cygwin.com>.\n");
804 printLicense (FILE * f
, char *name
)
807 "This program is free software: you can redistribute it and/or modify\n"
808 "it under the terms of the GNU General Public License as published by\n"
809 "the Free Software Foundation, either version 3 of the License, or\n"
810 "(at your option) any later version.\n\n"
811 "This program is distributed in the hope that it will be useful,\n"
812 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
813 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
814 "GNU General Public License for more details.\n\n"
815 "You should have received a copy of the GNU General Public License\n"
816 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n"
817 "See the COPYING file for full license information.\n");
821 usage (FILE * f
, char *name
)
823 poptPrintUsage (optCon
, f
, 0);
827 help (FILE * f
, char *name
)
829 printTopDescription (f
, name
);
830 poptPrintHelp (optCon
, f
, 0);
831 printBottomDescription (f
, name
);
835 version (FILE * f
, char *name
)
837 printTopDescription (f
, name
);
841 license (FILE * f
, char *name
)
843 printTopDescription (f
, name
);
844 printLicense (f
, name
);