OSDN Git Service

Correct a static string buffer aliasing issue.
[mingw/mingw-get.git] / src / setup.cpp
1 /*
2  * setup.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2013, MinGW.org Project
8  *
9  *
10  * Implementation of the mingw-get setup tool's dialogue controller.
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #define WIN32_LEAN_AND_MEAN
28 #define _WIN32_WINNT  0x0500
29
30 /* This component application does not support the DEBUGLEVEL feature
31  * of the main mingw-get application.
32  */
33 #undef  DEBUGLEVEL
34
35 #include "setup.h"
36 #include "guimain.h"
37 #include "pkgtask.h"
38 #include "dmh.h"
39
40 #include <stdio.h>
41 #include <unistd.h>
42 #include <process.h>
43 #include <limits.h>
44 #include <shlobj.h>
45 #include <wctype.h>
46 #include <wchar.h>
47
48 #include <sys/types.h>
49 #include <sys/stat.h>
50
51 #include <wtkalign.h>
52 #include <commctrl.h>
53
54 static void ViewLicence( void * )
55 {
56   /* Helper routine, dispatching requests via the MS-Windows shell,
57    * so that the user's choice of web browser is invoked to display
58    * the GNU General Public Licence, Version 3.
59    *
60    * Note that this function runs in its own thread, and requires
61    * COM services to be enabled...
62    */
63   CoInitialize( NULL );
64   /*
65    * ...so that the shell process may be invoked.
66    */
67   ShellExecute( NULL, NULL,
68       "http://www.gnu.org/licenses/gpl-3.0-standalone.html",
69       NULL, NULL, SW_SHOWNORMAL
70     );
71
72   /* The active thread terminates on return from this function;
73    * it no longer requires COM services.
74    */
75   CoUninitialize();
76 }
77
78 /* Embed procedures for creation of file system objects, and for
79  * constructing argument vectors for passing to child processes.
80  */
81 #include "mkpath.c"
82 #include "argwrap.c"
83
84 static POINT offset = { 0, 0 };
85 static int CALLBACK confirm( HWND dlg, unsigned msg, WPARAM request, LPARAM )
86 {
87   /* A generic dialogue box procedure which positions the dialogue
88    * box at a specified offset, then waits for any supported button
89    * click, returning the associated button identifier; this is used
90    * by BrowseLicenceDocument() to confirm requests to invoke the
91    * web browser to display the product licence.
92    */
93   switch( msg )
94   { case WM_INITDIALOG:
95       /*
96        * When first displaying the confirmation dialogue...
97        */
98       if( (offset.x | offset.y) != 0 )
99       {
100         /* ...adjust its position as specified, and ensure that
101          * it is presented as foremost in the z-order...
102          */
103         RECT whence;
104         GetWindowRect( dlg, &whence );
105         OffsetRect( &whence, offset.x, offset.y );
106         SetWindowPos( dlg, HWND_TOP,
107             whence.left, whence.top, 0, 0, SWP_NOSIZE
108           );
109       }
110       return TRUE;
111
112     case WM_COMMAND:
113       /* ...then close the dialogue, returning the identification
114        * of the button, as soon as any is clicked; (note that this
115        * assumes that the only active controls within the dialogue
116        * are presented as buttons).
117        */
118       EndDialog( dlg, LOWORD( request ) );
119       return TRUE;
120   }
121   return FALSE;
122 }
123
124 static inline void BrowseLicenceDocument( HWND dlg, long x = 0, long y = 0 )
125 {
126   /* Routine to dispatch requests to display the product licence;
127    * it is invoked when the user clicks the "View Licence" button,
128    * and responds by requesting confirmation before launching the
129    * internet browser in a separate thread, to display an online
130    * copy of the licence document.
131    */
132   offset.x = x; offset.y = y;
133   if( DialogBox( NULL,
134         MAKEINTRESOURCE( IDD_SETUP_LICENCE ), dlg, confirm ) == IDOK
135     ) _beginthread( ViewLicence, 0, NULL );
136 }
137
138 /* The following string constants are abstracted from pkgkeys.c;
139  * we redefine this minimal subset here, to avoid the overhead of
140  * importing the entire content of the pkgkeys module.
141  */
142 static const char *uri_key = "uri";
143 static const char *mirror_key = "mirror";
144 static const char *value_none = "none";
145
146 class SetupTool
147 {
148   /* Top level class, implementing the controlling features
149    * of the mingw-get setup tool.
150    */
151   public:
152     static int Status;
153
154     inline SetupTool( HINSTANCE );
155     static SetupTool *Invoke( void );
156     static unsigned int IsPref( unsigned int test ){ return test & prefs; }
157
158     bool InitialiseSetupHookAPI( void );
159     inline int DispatchSetupHookRequest( unsigned int, ... );
160
161   private:
162     const wchar_t *dll_name;
163     HMODULE base_dll, hook_dll;
164     typedef int (*dll_request_hook)( unsigned int, va_list );
165     dll_request_hook dll_request;
166
167     inline void DoFirstTimeSetup( HWND );
168     inline int SetInstallationPreferences( HWND );
169     inline HMODULE HaveWorkingInstallation( void );
170     inline wchar_t *UseDefaultInstallationDirectory( void );
171     inline void ShowInstallationDirectory( HWND );
172     inline int InstallationRequest( HWND );
173
174     static void EnablePrefs( HWND, int );
175     static inline void EnablePrefs( HWND, HWND );
176     static inline void StorePref( HWND, int, int );
177     static inline void ShowPref( HWND, int, int );
178
179     static SetupTool *setup_hook;
180     static const wchar_t *default_dirpath;
181     wchar_t *approot_path( const wchar_t * = NULL );
182     void ChangeInstallationDirectory( HWND );
183     static wchar_t *approot_tail;
184
185     static unsigned int prefs;
186     static int CALLBACK OpeningDialogue( HWND, unsigned, WPARAM, LPARAM );
187     static int CALLBACK ConfigureInstallationPreferences
188       ( HWND, unsigned, WPARAM, LPARAM );
189
190     static const wchar_t *gui_program;
191     int RunInstalledProgram( const wchar_t * );
192
193     inline wchar_t *setup_dll( void )
194     { /* Helper function to ensure that the static "approot_path" buffer
195        * specifies a reference path name for mingw-get-setup-0.dll
196        */
197       return approot_path( L"libexec\\mingw-get\\mingw-get-setup-0.dll" );
198     }
199
200     inline void CreateApplicationLauncher
201       ( int, const wchar_t *, const char *, const char * );
202 };
203
204 /* We will never instantiate more than one object of the SetupTool
205  * class at any one time.  Thus, we may record its status in a static
206  * member variable, which we must initialise; we assume the all setup
207  * operations will be completed successfully.
208  */
209 int SetupTool::Status = EXIT_SUCCESS;
210
211 /* We need an alternative status code, to indicate that the user has
212  * elected to run the standard installer immediately, on termination
213  * of the initial setup; the value is arbitrary, subject only to the
214  * requirement that it differs from EXIT_SUCCESS and EXIT_FAILURE.
215  */
216 #define EXIT_CONTINUE   101
217
218 /* Installation options are also recorded in a static member varaible;
219  * we initialise these to match a set of default preferences, which we
220  * believe will suit a majority of users.
221  */
222 unsigned int SetupTool::prefs = SETUP_OPTION_DEFAULTS;
223
224 /* We also provide a static hook, through which objects of any other
225  * class may invoke public methods of the SetupTool object, without
226  * requiring them to maintain their own reference to this object.
227  */
228 SetupTool *SetupTool::setup_hook = NULL;
229 SetupTool *SetupTool::Invoke( void ){ return setup_hook; }
230
231 static void RefreshDialogueWindow( HWND AppWindow )
232 {
233   /* A trivial helper routine, to force an immediate update
234    * of a dialogue window display.
235    */
236   InvalidateRect( AppWindow, NULL, FALSE );
237   UpdateWindow( AppWindow );
238 }
239
240 /* Embed an implementation of the diagnostic message handler,
241  * customised for deployment by the setup tool...
242  */
243 #include "dmhcore.cpp"
244 #include "dmhguix.cpp"
245
246 /* ...and supported by this custom initialisation routine.
247  */
248 EXTERN_C void dmh_init( const dmh_class subsystem, const char *progname )
249 {
250   /* Public entry point for message handler initialisation...
251    *
252    * We only do it once, silently ignoring any attempt to
253    * establish a second handler.
254    */
255   if( (dmh == NULL) && (subsystem == DMH_SUBSYSTEM_GUI) )
256     dmh = new dmhTypeGUI( strdup( progname ) );
257
258
259 inline unsigned long pkgSetupAction::HasAttribute( unsigned long mask )
260 {
261   /* Helper function to examine the flags within a setup action
262    * item, checking for the presence of a specified attribute.
263    */
264   return (this != NULL) ? mask & flags : 0UL;
265 }
266
267 void pkgSetupAction::ClearAllActions( void )
268 {
269   /* Routine to clear an entire list of setup action items,
270    * releasing the memory allocated to each.
271    */
272   pkgSetupAction *current = this;
273   while( current != NULL )
274   {
275     /* Walk the list, deleting each item in turn.
276      */
277     pkgSetupAction *defunct = current;
278     current = current->next;
279     delete defunct;
280   }
281 }
282
283 /* Worker thread API for use with modal dialogue boxes; the worker thread
284  * is invoked by passing a function pointer, conforming to this prototype,
285  * to the _beginthread() API.
286  */
287 typedef void SetupThreadProcedure( void * );
288
289 class SetupDialogueThread
290 {
291   /* Locally defined class to manage the binding of an arbitrary worker
292    * thread procedure to a modal dialogue, with a generic message loop.
293    */
294   public:
295     inline long ExitCode(){ return status; }
296     SetupDialogueThread( HWND, int, SetupThreadProcedure * );
297
298   private:
299     static int CALLBACK SetupDialogue( HWND, unsigned int, WPARAM, LPARAM );
300     static SetupThreadProcedure *WorkerThread;
301     long status;
302 };
303
304 /* We need to instantiate this static class member.
305  */
306 SetupThreadProcedure *SetupDialogueThread::WorkerThread = NULL;
307
308 int CALLBACK SetupDialogueThread::SetupDialogue
309 ( HWND window, unsigned int msg, WPARAM request, LPARAM )
310 {
311   /* Generic handler for dialogue boxes which delegate an associated
312    * processing activity to a background thread.
313    */
314   switch( msg )
315   {
316     /* We need to handle only two classes of windows messages
317      * on behalf of such dialogue boxes...
318      */
319     case WM_INITDIALOG:
320       /* ...viz. on initial dialogue box creation, we delegate the
321        * designated activity to the background thread...
322        */
323       _beginthread( WorkerThread, 0, (void *)(window) );
324       return TRUE;
325
326     case WM_COMMAND:
327       switch( msg = LOWORD( request ) )
328       {
329         case IDOK:
330         case IDCANCEL:
331           /* ...then we wait for a notification that the dialogue may be
332            * closed, (which isn't permitted until the thread completes).
333            */
334           EndDialog( window, msg );
335           return TRUE;
336
337         case ID_SETUP_SHOW_LICENCE:
338           /* Any request to view the licence document is handled in situ,
339            * without closing the active dialogue box.
340            */
341           BrowseLicenceDocument( window, -4, -50 );
342           return TRUE;
343       }
344   }
345   /* Any other messages, which are directed to this dialogue box,
346    * may be safely ignored.
347    */
348   return FALSE;
349 }
350
351 SetupDialogueThread::SetupDialogueThread
352 ( HWND owner, int id, SetupThreadProcedure *setup_actions )
353 {
354   /* Class constructor assigns the worker procedure for the thread,
355    * then opens the dialogue box which starts it, ultimately capturing
356    * the exit code when this dialogue is closed.
357    */
358   WorkerThread = setup_actions;
359   status = DialogBox( NULL, MAKEINTRESOURCE( id ), owner, SetupDialogue );
360 }
361
362 class SetupDownloadAgent
363 {
364   /* A locally defined class to manage package download activities
365    * on behalf of the setup tool.
366    */
367   public:
368     static void Run( void * );
369     SetupDownloadAgent( pkgSetupAction *packages ){ PackageList = packages; }
370
371   private:
372     static pkgSetupAction *PackageList;
373 };
374
375 /* Initialise the list of packages for download, as an empty list;
376  * we will subsequently populate it, as required.
377  */
378 pkgSetupAction *SetupDownloadAgent::PackageList = NULL;
379
380 /* Embed the download agent implementation as a filtered subset of
381  * mingw-get's own download service implementation.
382  */
383 #include "pkginet.cpp"
384 #include "pkgnget.cpp"
385
386 static inline int dll_load_failed( const wchar_t *dll_name )
387 {
388   /* Helper to diagnose failure to load any supporting DLL.
389    */
390   return dmh_notify( DMH_ERROR,
391       "%S: DLL load failed; cannot run setup hooks\n", dll_name
392     );
393 }
394
395 void SetupDownloadAgent::Run( void *owner )
396 {
397   /* Method to run the download agent; it fetches all packages
398    * which are specified in the setup tool's initial installation
399    * list, (using a metered download procedure)...
400    */
401   pkgDownloadMeterGUI metered( (HWND)(owner) );
402   PackageList->DownloadArchiveFiles();
403
404   /* ...then unpacks each into its ultimate installed location,
405    * before delegating finalisation of the installation to the
406    * installer DLLs, which should thus have been installed.
407    */
408   if(  (PackageList->UnpackScheduledArchives() == IDOK)
409   &&   SetupTool::Invoke()->InitialiseSetupHookAPI()     )
410   {
411     /* Only on successful initialisation of the DLL hooks, do
412      * we actually continue with the delegated installation.
413      */
414     SetupTool::Invoke()->DispatchSetupHookRequest(
415         SETUP_HOOK_POST_INSTALL, PackageList, owner
416       );
417
418     /* Successful DLL initialisation is also a prerequisite for
419      * allowing the user to run the main installer, to continue
420      * the installation with a more comprehensive package set;
421      * however, progression to such advanced installation...
422      */
423     if( SetupTool::IsPref( SETUP_OPTION_WITH_GUI ) )
424       /*
425        * ...is feasible, only if the user has elected to install
426        * the mingw-get GUI client.
427        */
428       EnableWindow( GetDlgItem( (HWND)(owner), IDOK ), TRUE );
429   }
430   else
431     /* DLL initialisation was unsuccessful; tell the user that
432      * we are unable to continue this installation.
433      */
434     dmh_notify( DMH_ERROR, "setup: unable to continue\n" );
435
436   /* In any event, always offer the option to quit, without
437    * proceeding to advanced installation.
438    */
439   EnableWindow( GetDlgItem( (HWND)(owner), IDCANCEL ), TRUE );
440 }
441
442 bool SetupTool::InitialiseSetupHookAPI( void )
443 {
444   /* Helper method, invoked by SetupDownloadAgent::Run(), to confirm
445    * that the requisite installer DLLs have been successfully unpacked,
446    * and if so, to initialise them for continuation of installation.
447    *
448    * First, we must confirm that mingw-get's main DLL is available...
449    */
450   if( (base_dll = HaveWorkingInstallation()) == NULL )
451     /*
452      * ...immediately abandoning the installation, if not.
453      */
454     return (dll_load_failed( dll_name ) == 0);
455
456   /* Having successfully loaded the main DLL, we perform a similar
457    * check for the setup tool's bridging DLL...
458    */
459   if( (hook_dll = LoadLibraryW( dll_name = setup_dll() )) == NULL )
460   {
461     /* ...once again, abandoning the installation, if this is not
462      * available; in this case, since we've already successfully
463      * loaded mingw-get's main DLL, we also unload it.
464      */
465     FreeLibrary( base_dll ); base_dll = NULL;
466     return (dll_load_failed( dll_name ) == 0);
467   }
468   /* With both DLLs successfully loaded, we may establish the
469    * conduit, through which the setup tool invokes procedures in
470    * either of these DLLs...
471    */
472   dll_request = (dll_request_hook)(GetProcAddress( hook_dll, "setup_hook" ));
473   if( dll_request != NULL )
474   {
475     /* ...and, on success, complete the DLL initialisation by
476      * granting the DLLs shared access to the diagnostic message
477      * handler provided by the setup tool's main program.
478      */
479     DispatchSetupHookRequest( SETUP_HOOK_DMH_BIND, dmh );
480     return true;
481   }
482   /* If we get to here, DLL initialisation was unsuccessful;
483    * diagnose, and bail out.
484    */
485   dmh_notify( DMH_ERROR, "setup_hook: DLL entry point not found\n" );
486   return false;
487 }
488
489 inline int SetupTool::DispatchSetupHookRequest( unsigned int request, ... )
490 {
491   /* Helper method, providing a variant argument API to the
492    * request hook within the setup tool's bridging DLL; it
493    * collects the argument vector, to pass into the DLL.
494    */
495   va_list argv;
496   va_start( argv, request );
497   int retval = dll_request( request, argv );
498   va_end( argv );
499   return retval;
500 }
501
502 static inline
503 long InitiatePackageInstallation( HWND owner, pkgSetupAction *pkglist )
504 {
505   /* Helper routine, invoked by SetupTool::DoFirstTimeSetup(), to
506    * install mingw-get, and proceed to further package selection.
507    */
508   SetupDownloadAgent agent( pkglist );
509   SetupDialogueThread run( owner, IDD_SETUP_DOWNLOAD, SetupDownloadAgent::Run );
510   RefreshDialogueWindow( owner );
511   return run.ExitCode();
512 }
513
514 /* Embed a selected subset of the package stream extractor methods,
515  * sufficient to support unpacking of the .tar.xz archives which are
516  * used as the delivery medium for mingw-get.
517  */
518 #include "pkgstrm.cpp"
519
520 /* The standard archive extractor expects to use the pkgOpenArchiveStream()
521  * function to gain access to archive files; however, the generalised version
522  * of this is excluded from the subset embedded from pkgstrm.cpp, so we must
523  * provide a replacement...
524  */
525 pkgArchiveStream *pkgOpenArchiveStream( const char *archive_path_name )
526 {
527   /* ...function to prepare an archive file for extraction; (this specialised
528    * version is specific to extraction of xz compressed archives).
529    */
530   return new pkgXzArchiveStream( archive_path_name );
531 }
532
533 /* The archive extractor classes incorporate reference pointers to pkgXmlNode
534  * and pkgManifest class objects, but these are not actually used within the
535  * context required here; to placate the compiler, declare these as opaque
536  * data types.
537  */
538 class pkgXmlNode;
539 class pkgManifest;
540
541 /* Embed the minimal subset of required extractor classes and methods...
542  */
543 #include "tarproc.cpp"
544
545 /* ...noting that the formal implementation of this required constructor
546  * has been excluded; (it implements a level of complexity, not required
547  * here); hence, we provide a minimal, do nothing, substitute.
548  */
549 pkgTarArchiveProcessor::pkgTarArchiveProcessor( pkgXmlNode * ){}
550
551 /* Similarly, we need a simplified implementation of the destructor
552  * for this same class...
553  */
554 pkgTarArchiveProcessor::~pkgTarArchiveProcessor()
555 {
556   /* ...but it this case, we still use it to clean up the memory
557    * allocated by the pkgTarArchiveExtractor class constructor.
558    */
559   free( (void *)(sysroot_path) );
560   delete stream;
561 }
562
563 static
564 int CALLBACK unwise( HWND owner, unsigned msg, WPARAM request, LPARAM data )
565 {
566   /* Handler for the dialogue box which is presented when the user
567    * makes an unwise choice of installation directory (specifically
568    * a choice with white space in the path name).
569    */
570   switch( msg )
571   {
572     case WM_INITDIALOG:
573       /* There are no particular initialisation requirements, for
574        * this dialogue box; just accept the defaults.
575        */
576       return TRUE;
577
578     case WM_COMMAND:
579       /* User has selected one of the continuation options...
580        */
581       switch( msg = LOWORD( request ) )
582       {
583         /* When it is...
584          */
585         case IDIGNORE:
586           /* Ignore advice to choose a more appropriate location:
587            * this requires additional confirmation that the user has
588            * understood the implications of this choice; do no more
589            * than enable the button which provides such confirmation.
590            */
591           if( HIWORD( request ) == BN_CLICKED )
592             EnableWindow( GetDlgItem( owner, IDYES ),
593                 SendMessage( (HWND)(data), BM_GETCHECK, 0, 0 ) == BST_CHECKED
594               );
595           return TRUE;
596
597         case IDNO:
598           /* Accept advice; go back to make a new choice of location
599            * for this installation, or...
600            */
601         case IDYES:
602           /* ...confirm understanding of, and intent to ignore, advice;
603            * in either case close the dialogue, returning appropriate
604            * selection code.
605            */
606           EndDialog( owner, msg );
607           return TRUE;
608       }
609   }
610   /* No other conditions are handled explicitly, within this dialogue.
611    */
612   return FALSE;
613 }
614
615 static bool UnconfirmedDirectorySelection( HWND owner, const wchar_t *choice )
616 {
617   /* Helper function to ratify user's selection of installation directory;
618    * given an absolute path name retrieved from SHBrowsForFolder(), it checks
619    * for the presence of embedded white space, and reiterates the appropriate
620    * warning when any is found, before allowing the user to choose again, or
621    * to ignore the advice, and shoot himself/herself in the foot.
622    */
623   const wchar_t *path;
624   RefreshDialogueWindow( owner );
625   if( choice && *(path = choice) )
626   {
627     /* We have a non-empty path name selection to ratify...
628      */
629     while( *path )
630       if( iswspace( *path++ ) )
631       {
632         /* ...and we DID find embedded white space; castigate the user
633          * for his/her folly in ignoring advice...
634          */
635         return DialogBox( NULL, MAKEINTRESOURCE( IDD_SETUP_UNWISE ),
636             owner, unwise ) != IDYES;
637       }
638     /* If we get to here, then the user appears to have made a sane choice;
639      * caller need not repeat the request for a directory selection...
640      */
641     return false;
642   }
643   /* ...but when we were asked to ratify no selection at all, then caller
644    * must repeat this request.
645    */
646   return true;
647 }
648
649 static
650 int CALLBACK BrowserInit( HWND owner, unsigned msg, LPARAM, LPARAM dirname )
651 {
652   /* Helper function to set the default installation directory path name,
653    * and to assign a more appropriate dialogue box description...
654    */
655   if( msg == BFFM_INITIALIZED )
656   {
657     /* ...when initialising the SHBrowseForFolder() dialogue to elicit
658      * the user's choice of installation directory.
659      */
660     SendMessage( owner, WM_SETTEXT, 0, (LPARAM)("Select Installation Directory") );
661     SendMessage( owner, BFFM_SETSELECTIONW, TRUE, dirname );
662   }
663   return 0;
664 }
665
666 static inline
667 bool mkdir_default( wchar_t *dirpath )
668 {
669   /* Helper function to create the default installation directory;
670    * this must exist before we call SHBrowseForFolder(), so that we
671    * may offer as the default choice, even if the user subsequently
672    * declines to accept it.
673    */
674   bool retval = false; struct _stat chkdir;
675   if( ! ((_wstat( dirpath, &chkdir ) == 0) && S_ISDIR( chkdir.st_mode )) )
676   {
677     /* The recommended default directory doesn't yet exist; try to
678      * create it...
679      */
680     if( ! (retval = (_wmkdir( dirpath ) == 0)) )
681       /*
682        * ...but if unsuccessful, fall back to offering the root
683        * directory of the default device, as a starting point for
684        * the user's selection.
685        */
686       dirpath[3] = L'\0';
687   }
688   /* In any event, this will return "true" if we created the
689    * directory, or "false" if it either already existed, or if
690    * our attempt to create it failed.
691    */
692   return retval;
693 }
694
695 static inline
696 void rmdir_default( const wchar_t *offer, const wchar_t *selected )
697 {
698   /* Helper function, called if we created the default installation
699    * directory; it will remove it if the user rejected this default
700    * selection, and any descendant thereof.
701    */
702   const wchar_t *chk = offer;
703   while( *chk && *selected && (*chk++ == *selected++) ) ;
704   if( (*chk != L'\0') || ((*selected != L'\0') && (*selected != L'\\')) )
705     _wrmdir( offer );
706 }
707
708 static
709 int setup_putenv( const char *varname, const wchar_t *value )
710 {
711   /* Helper function to assign a specified value to a named
712    * environment variable.
713    */
714   const char *fmt = "%s=%S";
715   char assignment[1 + snprintf( NULL, 0, fmt, varname, value )];
716   snprintf( assignment, sizeof( assignment ), fmt, varname, value );
717   return putenv( assignment );
718 }
719
720 wchar_t *SetupTool::approot_tail = NULL;
721 wchar_t *SetupTool::approot_path( const wchar_t *relative_path )
722 {
723   /* Helper method to resolve a relative path name to its absolute
724    * equivalent, within the directory hierarchy of the user's chosen
725    * installation directory.
726    *
727    * The resolved absolute path name is returned in this static
728    * buffer, initially defined to be empty.
729    */
730   static wchar_t dirpath[PATH_MAX] = L"\0";
731
732   /* When a previous call has left a "tail" buffer,
733    * (which may or may not be populated)...
734    */
735   if( approot_tail != NULL )
736     /*
737      * ...then clear it...
738      */
739     *approot_tail = L'\0';
740
741   else if( *dirpath != L'\0' )
742     /*
743      * ...otherwise, establish the "tail" buffer, following
744      * the "approot" string within the "dirpath" buffer.
745      */
746     for( approot_tail = dirpath; *approot_tail != L'\0'; approot_tail++ )
747       ;
748
749   /* When a relative path argument is specified...
750    */
751   if( relative_path != NULL )
752   {
753     /* ...then we must resolve it as an absolute path, interpreting
754      * it as relative to "approot", by copying it into the "tail"
755      * buffer within "dirpath".
756      *
757      * We perform the copy character by character, storing at the
758      * advancing "caret" position, while ensuring we do not overrun
759      * the "endstop" location, which we set initially to mark the
760      * end of the "dirpath" buffer.
761      */
762     wchar_t *caret = approot_tail - 1;
763     wchar_t *endstop = dirpath + PATH_MAX - 1;
764
765     /* We initially set "caret" to point to the final character
766      * in "approot", so that we may check for the presence of a
767      * directory separator; if there ISN'T one there already...
768      */
769     if( (*caret != L'\\') && (*caret != L'/') )
770       /*
771        * ...then we advance it to the start of the "tail"
772        * buffer, where we will insert one.
773        */
774       ++caret;
775
776     /* When the first character of the specified relative path
777      * is NOT itself a directory separator, (which is expected
778      * in the normal case)...
779      */
780     if( (*relative_path != L'\\') && (*relative_path != L'/') )
781       /*
782        * ...then we explicitly insert one; (we may note that this
783        * will either overwrite an existing separator at the end of
784        * "approot", or it will occupy the first "caret" position
785        * within the "tail" buffer).
786        */
787       *caret++ = L'\\';
788
789     /* Now, we copy the relative path to the "caret" location...
790      */
791     do { /* ...ensuring that we do NOT overrun the "endstop"...
792           */
793          if( caret < endstop )
794          {
795            /* ...and normalising directory separators, favouring
796             * Microsoft's '\\' preference, as we go...
797             */
798            if( (*caret = *relative_path++) == L'/' )
799              *caret = L'\\';
800          }
801          else
802            /* ...but, if we hit the "endstop", we immediately
803             * truncate and terminate the copy...
804             */
805            *caret = L'\0';
806
807          /* ...continuing until we've copied the terminating NUL
808           * from the relative path argument itself, or we hit the
809           * "endstop", forcing early termination.
810           */
811        } while( *caret++ != L'\0' );
812   }
813   /* However we completed the operation, we return a pointer to the
814    * entire content of the "dirpath" static buffer.
815    */
816   return dirpath;
817 };
818
819 inline int pkgSetupAction::UnpackScheduledArchives( void )
820 {
821   /* Helper to unpack each of the package archives, specified in the
822    * setup actions list, extracting content to its ultimate installed
823    * location within the user specified installation directory tree.
824    */
825   int status = IDCANCEL;
826   pkgSetupAction *install;
827
828   /* When the action list is not empty...
829    */
830   if( (install = this) != NULL )
831   {
832     /* ...ensure that we process it from its first entry.
833      */
834     status = IDOK;
835     while( install->prev != NULL ) install = install->prev;
836     while( install != NULL )
837     {
838       /* For each list entry, locate the downloaded copy of the
839        * archive, in the installer's package cache directory.
840        */
841       const char *cache = "%R" "var\\cache\\mingw-get\\packages\\%F";
842       char archive_name[mkpath( NULL, cache, install->ArchiveName(), NULL )];
843       mkpath( archive_name, cache,  install->ArchiveName(), NULL );
844
845       /* Report activity, and provided download has made the
846        * cached copy of the archive available...
847        */
848       dmh_notify( DMH_INFO, "setup: unpacking %s\n", install->ArchiveName() );
849       if( install->HasAttribute( ACTION_DOWNLOAD ) == 0 )
850         /*
851          * ...extract its content.
852          */
853         pkgTarArchiveExtractor( archive_name, "%R" );
854
855       else
856       { /* The package is not available; assume that download was
857          * unsuccessful, and diagnose accordingly...
858          */
859         dmh_notify( DMH_ERROR, "unpack: required archive file is not available\n" );
860         dmh_notify( DMH_ERROR, "unpack: aborted due to previous download failure\n" );
861
862         /* ...then cancel further installation activities.
863          */
864         status = IDCANCEL;
865       }
866
867       /* Repeat for any other scheduled action items.
868        */
869       install = install->next;
870     }
871   }
872   /* Ultimately return the status code, indicating either complete
873    * success, or requesting cancellation of further installation.
874    */
875   return status;
876 }
877
878 static
879 char *arg_append( char *caret, const char *argtext, const char *endstop )
880 {
881   /* Command line construction helper function; append specified "argtext"
882    * into the constructed command line, with appropriate quoting to keep it
883    * as a single argument, and preceded by a space, at the position marked
884    * by "caret", and ensuring that the text does not overrun "endstop".
885    */
886   if( (endstop > caret) && ((endstop - caret) > 1)
887   &&  (argwrap( caret + 1, argtext, endstop - caret - 1) != NULL)  )
888   {
889     /* The specified argument can be accommodated; insert the separating
890      * space, and update the caret to mark the position at which the next
891      * argument, if any, should be appended.
892      */
893     *caret = '\040';
894     return caret + strlen( caret );
895   }
896   /* If we get to here, the argument could not be accommodated, return the
897    * caret position, unchanged.
898    */
899   return caret;
900 }
901
902 static
903 char *arg_append( char *caret, const wchar_t *argtext, const char *endstop )
904 {
905   /* Overloaded variant of the preceding command line construction helper;
906    * this variant appends an argument, specified as a wide character string,
907    * to the constructed command line.
908    */
909   if( (endstop > caret) && ((endstop - caret) > 1) )
910   {
911     /* There is at least some remaining space in the command line buffer;
912      * convert the specified argument to a regular string...
913      */
914     const char *conv_fmt = "%S";
915     char conv_buf[1 + snprintf( NULL, 0, conv_fmt, argtext )];
916     snprintf( conv_buf, sizeof( conv_buf ), conv_fmt, argtext );
917     /*
918      * ...then delegate the actual append operation to the regular string
919      * handling variant of the helper function.
920      */
921     return arg_append( caret, conv_buf, endstop );
922   }
923   /* If we get to here, the command line buffer space has been completely
924    * exhausted; return the caret position, unchanged.
925    */
926   return caret;
927 }
928
929 #ifndef ARG_MAX
930 /* POSIX specifies this as the maximum number of characters allowed in the
931  * aggregate of all arguments passed to a child process in an exec() function
932  * call.  It isn't typically defined on MS-Windows, although limits do apply
933  * in the case of exec(), spawn(), or system() function calls.  MSDN tells
934  * us that the maximum length of a command line is 8191 characters on WinXP
935  * and later, but only 2047 characters on earlier versions; allowing one
936  * additional character for a terminating NUL, we will adopt the lower
937  * of these, as a conservative choice.
938  */
939 # define ARG_MAX  2048
940 #endif
941
942 inline void SetupTool::CreateApplicationLauncher
943 ( int opt, const wchar_t *appname, const char *desc, const char *linkname )
944 {
945   /* Construct a command line to invoke the shlink.js script, (which is
946    * provided by the mingw-get package), via the system provided wscript
947    * interpreter, to install a desktop or start-menu application link.
948    */
949   static const char *cmd = "wscript";
950   static const char *location[] = { "--start-menu", "--desktop" };
951
952   /* Initialise the argument list, setting the wscript option to suppress
953    * its normal verbose behaviour, and set a buffer endstop reference, so
954    * that we may detect, and avoid overrun.
955    */
956   char cmdline[ARG_MAX] = "-nologo";
957   const char *endstop = cmdline + sizeof( cmdline );
958
959   /* Add the full path name reference for the shlink.js script which is to
960    * be executed; (note that we resolve this relative to the user's choice
961    * of installation directory, where the script will have been installed,
962    * during prior unpacking of the mingw-get-bin package tarball).
963    */
964   char *caret = arg_append( cmdline + strlen( cmdline ),
965       approot_path( L"libexec\\mingw-get\\shlink.js" ), endstop
966     );
967
968   /* Add options to select placement of the application links, and to specify
969    * whether they should be available to current user ot to all users.
970    */
971   caret = arg_append( ((opt & SETUP_OPTION_ALL_USERS) != 0)
972       ? arg_append( caret, "--all-users", endstop )
973       : caret, location[opt >> 2], endstop
974     );
975
976   /* Add a description for the application link.
977    */
978   caret = arg_append( arg_append( caret, "--description", endstop ),
979       desc, endstop
980     );
981
982   /* Specify the path name for the application to be invoked, (again noting
983    * that this must be resolved relative to the installation directory), and
984    * the name to be given to the link file itself.
985    */
986   arg_append( arg_append( caret, approot_path( appname ), endstop ),
987       linkname, endstop
988     );
989
990   /* Finally, invoke the script interpreter to process the specified command.
991    * (We use a spawn() call here, in preference to system(), to avoid opening
992    *  any unwanted console windows).
993    */
994   spawnlp( P_WAIT, cmd, cmd, cmdline, NULL );
995 }
996
997 inline SetupTool::SetupTool( HINSTANCE Instance ):
998 base_dll( NULL ), hook_dll( NULL )
999 {
1000   /* Constructor for the SetupTool object.  Note that there should be only
1001    * one such object instantiated; this is intended as a one time only call,
1002    * and any attempted subsequent call will be ignored, as a consequence of
1003    * the first time initialisation of the setup_hook static member.
1004    */
1005   if( setup_hook == NULL )
1006   {
1007     setup_hook = this;
1008     UseDefaultInstallationDirectory();
1009
1010     /* Get a security token for the process, so that we may selectively
1011      * disable those options which require administrative privilege, when
1012      * running as a normal user.
1013      *
1014      * FIXME: this is specific to WinNT platforms; we should add a DLL
1015      * lookup, so we can avoid calling the security hooks which are not
1016      * supported on Win9x.
1017      */
1018     void *group = NULL;
1019     SID_IDENTIFIER_AUTHORITY sid = SECURITY_NT_AUTHORITY;
1020     int authority = AllocateAndInitializeSid( &sid, 2,
1021         SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
1022         &group
1023       );
1024     if( authority != 0 )
1025     {
1026       if( CheckTokenMembership( NULL, group, &authority ) && (authority != 0) )
1027         prefs |= SETUP_OPTION_PRIVILEGED | SETUP_OPTION_ALL_USERS;
1028
1029       FreeSid( group );
1030     }
1031     /* To offer the user a choice of installation directory, we will
1032      * use the SHBrowswForFolder() API; this is a COM component API,
1033      * so we must initialise the COM subsystem.
1034      */
1035     CoInitialize( NULL );
1036
1037     /* Initialise our own Diagnostic Message Handler subsystem.
1038      */
1039     dmh_init( DMH_SUBSYSTEM_GUI,
1040         WTK::StringResource( Instance, ID_MAIN_WINDOW_CLASS )
1041       );
1042
1043     /* Make ALL of Microsoft's common controls available.
1044      */
1045     InitCommonControls();
1046
1047     /* The setup tool runs as a sequence of dialogue boxes, without
1048      * any main window; display the opening dialogue.
1049      */
1050     DialogBox( Instance,
1051         MAKEINTRESOURCE( IDD_SETUP_BEGIN ), NULL, OpeningDialogue
1052       );
1053
1054     /* If the mingw-get-0.dll and mingw-get-setup-0.dll libraries
1055      * were successfully loaded, to complete the installation process,
1056      * then we must now unload them; we also have no further use for
1057      * mingw-get-setup-0.dll, so we may delete it.
1058      */
1059     if( hook_dll != NULL ){ FreeLibrary( hook_dll ); _wunlink( setup_dll() ); }
1060     if( base_dll != NULL ){ FreeLibrary( base_dll ); }
1061
1062     /* We're done with the COM subsystem; release it.
1063      */
1064     CoUninitialize();
1065
1066     /* When the user has requested progression to advanced installation...
1067      */
1068     if( Status == EXIT_CONTINUE )
1069     {
1070       /* ...the delegate that to mingw-get itself, before reasserting
1071        * the successful completion status for the setup tool.
1072        */
1073       RunInstalledProgram( gui_program );
1074       Status = EXIT_SUCCESS;
1075     }
1076   }
1077 }
1078
1079 const wchar_t *SetupTool::default_dirpath = L"C:\\MinGW";
1080 inline wchar_t *SetupTool::UseDefaultInstallationDirectory( void )
1081 {
1082   /* Set up the specification for the preferred default installation
1083    * directory; (this is per MinGW.org Project recommendation).
1084    */
1085   approot_tail = NULL;
1086   return wcscpy( approot_path(), default_dirpath );
1087 }
1088
1089 void SetupTool::ChangeInstallationDirectory( HWND AppWindow )
1090 {
1091   /* Set up the specification for the preferred default installation
1092    * directory; (this is per MinGW.org Project recommendation).
1093    */
1094   wchar_t *dirpath = approot_path();
1095
1096   /* Exert our best effort to make the default installation directory
1097    * available for immediate selection via SHBrowseForFolder()
1098    */
1099   bool default_dir_created = mkdir_default( dirpath );
1100
1101   /* The browse for folder dialogue isn't modal; to ensure the user
1102    * doesn't inadvertently dismiss the invoking dialogue prematurely,
1103    * disable its active control buttons.
1104    */
1105   int buttons[] = { IDOK, ID_SETUP_SHOW_LICENCE, ID_SETUP_CHDIR, IDCANCEL };
1106   for( int index = 0; index < 4; index++ )
1107   {
1108     /* Note that we could simply have disabled the invoking dialogue
1109      * box itself, but this loop to disable individual buttons looks
1110      * better, giving the user better visual feedback indicating the
1111      * disabled state of the owner window's control buttons.
1112      */
1113     buttons[index] = (int)(GetDlgItem( AppWindow, buttons[index] ));
1114     EnableWindow( (HWND)(buttons[index]), FALSE );
1115   }
1116
1117   /* Set up a standard "Browse for Folder" dialogue, to allow the
1118    * user to choose an installation directory...
1119    */
1120   BROWSEINFOW explorer = { 0 };
1121   explorer.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI;
1122   explorer.lpszTitle = L"Please select a directory, in which to install MinGW; "
1123     L"it is strongly recommended that your choice should avoid any directory "
1124     L"which includes white space within its absolute path name.";
1125   explorer.lParam = (LPARAM)(dirpath);
1126   explorer.lpfn = BrowserInit;
1127
1128   /* ...and invoke it, possibly repeatedly, until the user has
1129    * confirmed an appropriate, (or maybe inappropriate), choice.
1130    */
1131   do { LPITEMIDLIST dir;
1132        if( (dir = SHBrowseForFolderW( &explorer )) != NULL )
1133          SHGetPathFromIDListW( dir, dirpath );
1134
1135        /* Fow each proposed directory choice returned from the
1136         * browser dialogue, force a rescan to locate the end of
1137         * its name, in the "approot" return buffer...
1138         */
1139        approot_tail = NULL;
1140        /*
1141         * ...then update the preferences display, to reflect the
1142         * current choice.
1143         */
1144        ShowInstallationDirectory( AppWindow );
1145
1146        /* Finally, check for any possibly ill advised directory
1147         * choice, (specifically warning of embedded white space,
1148         * and give the user an opportunity for remedial action.
1149         */
1150      } while( UnconfirmedDirectorySelection( AppWindow, dirpath ) );
1151
1152   /* When we created the default installation directory, in preparation
1153    * for a default (recommended) install...
1154    */
1155   if( default_dir_created )
1156     /*
1157      * ...we check if the user accepted this choice, and remove it when
1158      * some alternative (suitable or otherwise) was adopted.
1159      */
1160     rmdir_default( default_dirpath, dirpath );
1161
1162   /* The non-modal directory browser window has now been closed, but our
1163    * dialogue still exhibits disabled controls.  Although we disabled each
1164    * control individually, the reverse process of reactivating each of them
1165    * individually seems to not, and subsequently restoring the focus to the
1166    * default "Continue", doesn't seem to properly restore the visual cue to
1167    * apprise the user of this default; thus we send a WM_COMMAND message to
1168    * close this dialogue, while requesting that it be reinitialised from
1169    * scratch, (so reactivating it in its entirety).
1170    */
1171   SendMessage( AppWindow, WM_COMMAND, (WPARAM)(IDRETRY), 0 );
1172 }
1173
1174 inline void SetupTool::DoFirstTimeSetup( HWND AppWindow )
1175 #define archive_class(TAG)  WTK::StringResource( NULL, ID_##TAG##_DISTNAME )
1176 {
1177   /* When we didn't defer to any prior installation, proceed with a new
1178    * one; set up the APPROOT environment variable, to reflect the choice
1179    * of installation directory, (whether user's choice or default).
1180    */
1181   setup_putenv( "APPROOT", approot_path( L"" ) );
1182
1183   /* Identify the minimal set of packages, which we require to establish
1184    * the mingw-get installer set-up; we specify this a a linked action list,
1185    * beginning with a setup action request for the base package...
1186    */
1187   pkgSetupAction *linked, *list;
1188   linked = list = new pkgSetupAction( NULL, archive_class( PACKAGE_BASE ) );
1189   if( IsPref( SETUP_OPTION_WITH_GUI ) )
1190     /*
1191      * ...optionally adding the GUI extension, at the user's behest...
1192      */
1193     linked = new pkgSetupAction( linked, archive_class( PACKAGE_GUI ) );
1194
1195   /* ...and finishing up with the setup DLL and XML data packages.
1196    */
1197   linked = new pkgSetupAction( linked, archive_class( PACKAGE_DATA ), "dll" );
1198   linked = new pkgSetupAction( linked, archive_class( PACKAGE_DATA ), "xml" );
1199
1200   /* Download packages to the mingw-get package cache, (which we will
1201    * create, as a side effect of downloading, if necessary), and unpack
1202    * them in place, to establish the basic mingw-get installation.
1203    */
1204   if( InitiatePackageInstallation( AppWindow, list ) == IDOK )
1205     Status = EXIT_CONTINUE;
1206
1207   /* Finally, clean up and we are done.
1208    */
1209   list->ClearAllActions();
1210 }
1211
1212 #define SETUP_OPTION(M)  SetupTool::IsPref(SETUP_OPTION_##M)
1213 #define SETUP_ASSERT(M)  SETUP_OPTION(M) ? BST_CHECKED : BST_UNCHECKED
1214 #define SETUP_REVERT(M)  SETUP_OPTION(M) ? BST_UNCHECKED : BST_CHECKED
1215
1216 inline void SetupTool::ShowPref( HWND window, int id, int option )
1217 {
1218   /* Helper method to update the display of any check box control,
1219    * within the installation preferences dialogue box, to match the
1220    * state of its associated setting flag within the setup object.
1221    */
1222   SendMessage( GetDlgItem( window, id ), BM_SETCHECK, option, 0 );
1223 }
1224
1225 inline void SetupTool::StorePref( HWND ctrl, int id, int option )
1226 {
1227   /* Helper method to record any user specified change of state
1228    * in any check box control, within the installation preferences
1229    * dialogue box, to its associated flag within the setup object.
1230    */
1231   if( SendMessage( GetDlgItem( ctrl, id ), BM_GETCHECK, 0, 0 ) == BST_CHECKED )
1232     /*
1233      * The user selected the option associated with the check box;
1234      * set the associated flag to its "on" state.
1235      */
1236     prefs |= option;
1237
1238   else
1239     /* The user cleared the check box; set the associated flag to
1240      * its "off" state.
1241      */
1242     prefs &= ~option;
1243 }
1244
1245 void SetupTool::EnablePrefs( HWND dialogue, int state )
1246 {
1247   /* Helper used by the ConfigureInstallationPreferences() method,
1248    * to set the enabled state for those selection controls which are
1249    * only meaningful when a particular controlling selection, (which
1250    * is nominally the choice to install the GUI), is in effect.
1251    */
1252   if( IsPref( SETUP_OPTION_PRIVILEGED ) )
1253   {
1254     /* The "all users" installation option is supported only
1255      * if the setup tool is run with administrator privilege.
1256      */
1257     EnableWindow( GetDlgItem( dialogue, ID_SETUP_ALL_USERS ), state );
1258   }
1259   /* All other options are available to everyone.
1260    */
1261   EnableWindow( GetDlgItem( dialogue, ID_SETUP_ME_ONLY ), state );
1262   EnableWindow( GetDlgItem( dialogue, ID_SETUP_START_MENU ), state );
1263   EnableWindow( GetDlgItem( dialogue, ID_SETUP_DESKTOP ), state );
1264 }
1265
1266 inline void SetupTool::EnablePrefs( HWND dialogue, HWND ref )
1267 {
1268   /* Overload the preceding EnablePrefs() method, so we can deduce
1269    * the state parameter from a reference check-box control.
1270    */
1271   EnablePrefs( dialogue, SendMessage( ref, BM_GETCHECK, 0, 0 ) );
1272 }
1273
1274 inline HMODULE SetupTool::HaveWorkingInstallation( void )
1275 {
1276   /* Helper method to check for a prior installation, by attempting
1277    * to load its installed DLL component into our process context.
1278    */
1279   dll_name = approot_path( L"libexec\\mingw-get\\mingw-get-0.dll" );
1280   return LoadLibraryW( dll_name );
1281 }
1282
1283 /* Identify the path to the mingw-get GUI client program, relative
1284  * to the root of the mingw-get installation tree.
1285  */
1286 const wchar_t *SetupTool::gui_program = L"libexec\\mingw-get\\guimain.exe";
1287
1288 int SetupTool::RunInstalledProgram( const wchar_t *program )
1289 {
1290   /* Helper method to spawn an external process, into which a
1291    * specified program image is loaded; (typically this will be
1292    * the mingw-get program, either inherited from a previous run
1293    * of this setup tool, or a copy which we have just installed).
1294    */
1295   int status;
1296
1297   /* We assume that the program to run lives within the directory
1298    * heirarchy specified for mingw-get installation.
1299    */
1300   program = approot_path( program );
1301   if( (status = _wspawnl( _P_NOWAIT, program, program, NULL )) != -1 )
1302   {
1303     /* When the specified image has been successfully loaded, we
1304      * give it around 1500 ms to get running, then we promote it
1305      * to the foreground.
1306      */
1307     Sleep( 1500 ); WTK::RaiseAppWindow( NULL, ID_MAIN_WINDOW_CLASS );
1308   }
1309   /* The return value is that returned by the spawn request.
1310    */
1311   return status;
1312 }
1313
1314 static inline int MessageDialogue( HINSTANCE app, int id, HWND owner )
1315 {
1316   /* Helper function to emulate MessageBox behaviour using an
1317    * application specified dialogue box resource.
1318    */
1319   return DialogBox( app, MAKEINTRESOURCE( id ), owner, confirm );
1320 }
1321
1322 int CALLBACK SetupTool::ConfigureInstallationPreferences
1323 ( HWND window, unsigned int msg, WPARAM request, LPARAM data )
1324 {
1325   /* Handler for user interaction with the installation preferences
1326    * dialogue; configures options for the ensuing installation.
1327    */
1328   HMODULE dll_hook;
1329   switch( msg )
1330   {
1331     /* We need to handle only two classes of message...
1332      */
1333     case WM_INITDIALOG:
1334       /*
1335        * On initialisation of the dialogue, we ensure that the
1336        * displayed state of the controls is consistent with the
1337        * default, or previous settings, as specified within the
1338        * SetupTool object.
1339        */
1340       EnablePrefs( window, IsPref( SETUP_OPTION_WITH_GUI ) );
1341       ShowPref( window, ID_SETUP_WITH_GUI, SETUP_ASSERT( WITH_GUI ) );
1342       ShowPref( window, ID_SETUP_ME_ONLY, SETUP_REVERT( ALL_USERS ) );
1343       ShowPref( window, ID_SETUP_ALL_USERS, SETUP_ASSERT( ALL_USERS ) );
1344       ShowPref( window, ID_SETUP_START_MENU, SETUP_ASSERT( START_MENU ) );
1345       ShowPref( window, ID_SETUP_DESKTOP, SETUP_ASSERT( DESKTOP ) );
1346       Invoke()->ShowInstallationDirectory( window );
1347
1348       /* Explicitly assign focus to the "Continue" button, and
1349        * return FALSE so the default message handler will not
1350        * override this with any default choice.
1351        */
1352       SetFocus( GetDlgItem( window, IDOK ) );
1353       return FALSE;
1354
1355     case WM_COMMAND:
1356       switch( msg = LOWORD( request ) )
1357       {
1358         /* A small selection of WM_COMMAND class messages
1359          * requires our attention.
1360          */
1361         case ID_SETUP_WITH_GUI:
1362           if( HIWORD( request ) == BN_CLICKED )
1363             /*
1364              * The user has toggled the state of the GUI installation
1365              * option; we intercept this, so we may assign appropriate
1366              * availablity of other preferences...
1367              */
1368             EnablePrefs( window, (HWND)(data) );
1369
1370           /* ...but otherwise, we leave the default message handler to
1371            * process this in its default manner.
1372            */
1373           return FALSE;
1374
1375         case ID_SETUP_CHDIR:
1376           /* The user has selected the option to change the directory
1377            * into which mingw-get is to be installed; we handle this
1378            * request explicitly...
1379            */
1380           Invoke()->ChangeInstallationDirectory( window );
1381           /*
1382            * ...with no further demand on the default message handler.
1383            */
1384           return TRUE;
1385
1386         case IDOK:
1387           /* The user has clicked the "Continue" button, (presumably
1388            * after making an appropriate selection of his installation
1389            * preferences; before blindly proceeding to installation,
1390            * check for any previously existing DLL component...
1391            */
1392           if( (dll_hook = Invoke()->HaveWorkingInstallation()) != NULL )
1393           {
1394             /* ...and, when one exists and has been loaded into our
1395              * runtime context, unload it before offering a choice of
1396              * how to continue...
1397              */
1398             FreeLibrary( dll_hook );
1399             switch( msg = MessageDialogue( NULL, IDD_SETUP_EXISTS, window ) )
1400             {
1401               case IDRETRY:
1402                 /* The user has indicated a preference to continue
1403                  * installation, but with an alternative installation
1404                  * directory, so that the existing installation may
1405                  * be preserved; go back to change directory.
1406                  */
1407                 Invoke()->ChangeInstallationDirectory( window );
1408                 break;
1409
1410               case ID_SETUP_RUN:
1411                 /* The user has indicated a preference to switch to
1412                  * advanced installation mode, by running the existing
1413                  * copy of mingw-get; attempt to do so, before falling
1414                  * through to cancel this setup session.
1415                  */
1416                 Invoke()->RunInstalledProgram( gui_program );
1417
1418               case IDCANCEL:
1419                 /* The current setup session is to be cancelled; pass
1420                  * the cancellation request down the dialogue stack.
1421                  */
1422                 EndDialog( window, IDCANCEL );
1423                 return TRUE;
1424             }
1425           }
1426         case IDRETRY:
1427         case ID_SETUP_SHOW_LICENCE:
1428           /* we propagate his chosen options back to
1429            * the SetupTool object...
1430            */
1431           StorePref( window, ID_SETUP_DESKTOP, SETUP_OPTION_DESKTOP );
1432           StorePref( window, ID_SETUP_START_MENU, SETUP_OPTION_START_MENU );
1433           StorePref( window, ID_SETUP_ALL_USERS, SETUP_OPTION_ALL_USERS );
1434           StorePref( window, ID_SETUP_WITH_GUI, SETUP_OPTION_WITH_GUI );
1435           /*
1436            * ...before simply falling through...
1437            */
1438         case IDCANCEL:
1439           /* ...to accept the chosen preferences when the user clicked
1440            * "Continue", or to discard them on "Cancel"; in either case
1441            * we close the dialogue box, passing the closure method ID
1442            * back to the caller...
1443            */
1444           EndDialog( window, msg );
1445           /*
1446            * ...while informing the default message handler that we do
1447            * not require it to process this message.
1448            */
1449           return TRUE;
1450       }
1451   }
1452   /* Any other messages are simply ignored, leaving the default
1453    * message handler to process them.
1454    */
1455   return FALSE;
1456 }
1457
1458 inline void SetupTool::ShowInstallationDirectory( HWND dialogue )
1459 {
1460   /* Helper method to display the active choice of installation directory
1461    * within the preferences configuration dialogue.
1462    */
1463   HWND viewport;
1464   if( (viewport = GetDlgItem( dialogue, ID_SETUP_CURDIR )) != NULL )
1465     SendMessageW( viewport, WM_SETTEXT, 0, (LPARAM)(approot_path()) );
1466 }
1467
1468 inline int SetupTool::SetInstallationPreferences( HWND AppWindow )
1469 {
1470   /* Helper method to display a dialogue box, offering the user
1471    * a choice of installation preferences.
1472    */
1473   return DialogBox( NULL, MAKEINTRESOURCE( IDD_SETUP_OPTIONS ), AppWindow,
1474       ConfigureInstallationPreferences
1475     );
1476 }
1477
1478 inline int SetupTool::InstallationRequest( HWND AppWindow )
1479 {
1480   /* Method to process requests to proceed with installation, on user
1481    * demand from the opening dialogue box.
1482    *
1483    * First step is to garner installation preferences from the user.
1484    */
1485   int request;
1486   do { if( (request = SetInstallationPreferences( AppWindow )) == IDOK )
1487        {
1488          /* When the user is satisfied that all options have been
1489           * appropriately specified, apply them.
1490           */
1491          DoFirstTimeSetup( AppWindow );
1492          const WTK::StringResource description( NULL, ID_MAIN_WINDOW_CAPTION );
1493          if( IsPref( SETUP_OPTION_WITH_GUI ) )
1494            for( int i = 2; i < 6; i++ )
1495            {
1496              /* When the user has requested the creation of program
1497               * "shortcuts", create them as appropriate.
1498               */
1499              if( (IsPref( i ) | IsPref( SETUP_OPTION_ALL_USERS )) == i )
1500                CreateApplicationLauncher( i, gui_program, description,
1501                    (i & SETUP_OPTION_DESKTOP) ? "MinGW Installer" : description
1502                  );
1503            }
1504        }
1505        /* Continue collecting preference settings, until the user elects to
1506         * invoke the "continue" action.
1507         */
1508      } while( request == IDRETRY );
1509   return request;
1510 }
1511
1512 int CALLBACK SetupTool::OpeningDialogue
1513 ( HWND dlg, unsigned int msg, WPARAM request, LPARAM )
1514 {
1515   /* Procedure to manage the initial dialogue box, which serves as
1516    * the main window for the setup application.
1517    */
1518   switch( msg )
1519   { case WM_INITDIALOG:
1520       /* Invoked when the dialogue box is first displayed; centre it
1521        * on-screen, and assign an appropriate caption.
1522        */
1523       WTK::AlignWindow( dlg, WTK_ALIGN_CENTRED | WTK_ALIGN_ONSCREEN );
1524       ChangeCaption( dlg, WTK::StringResource( NULL, ID_MAIN_DIALOGUE_CAPTION ) );
1525       return TRUE;
1526
1527     case WM_COMMAND:
1528       /* Handle requests from user interaction with the controls which
1529        * are provided within the dialogue box.
1530        */
1531       switch( msg = LOWORD( request ) )
1532       {
1533         case IDOK:
1534           /* User has elected to proceed with installation; inhibit
1535            * further control interaction, and process the request.
1536            */
1537           EnableWindow( GetDlgItem( dlg, IDOK ), FALSE );
1538           EnableWindow( GetDlgItem( dlg, IDCANCEL ), FALSE );
1539           while( Invoke()->InstallationRequest( dlg ) == ID_SETUP_SHOW_LICENCE )
1540             /*
1541              * During installation, the user may request to view
1542              * the product licence; honour such requests.
1543              */
1544             BrowseLicenceDocument( dlg );
1545
1546         case IDCANCEL:
1547           /* Invoked immediately, when the user elects to cancel the
1548            * installation, otherwise by fall through on completion of
1549            * a request to proceed; this closes the top-level dialogue,
1550            * so terminating the application/
1551            */
1552           EndDialog( dlg, msg );
1553           return TRUE;
1554
1555         case ID_SETUP_SHOW_LICENCE:
1556           /* This represents a confirmation that the user would like
1557            * to view the licence document for the package.
1558            */
1559           BrowseLicenceDocument( dlg );
1560           return TRUE;
1561       }
1562   }
1563   /* Other messages to the opening dialogue box may be safely ignored.
1564    */
1565   return FALSE;
1566 }
1567
1568 pkgSetupAction::pkgSetupAction
1569 ( pkgSetupAction *queue, const char *pkg, const char *ext ):
1570 flags( ACTION_DOWNLOAD + ACTION_INSTALL ), prev( queue ), next( NULL )
1571 {
1572   /* Constructor for a setup action item; having initialised the
1573    * flags, and the link reference to the first entry, if any, in
1574    * the actions list...
1575    */
1576   if( ext != NULL )
1577   {
1578     /* ...it constructs the name of the package with which the action
1579      * is to be associated, qualified by the specified extension, and
1580      * copies it to the heap...
1581      */
1582     char ref[1 + snprintf( NULL, 0, pkg, ext )]; sprintf( ref, pkg, ext );
1583     package_name = strdup( ref );
1584   }
1585   else
1586     /* ...or, when no qualifying extension is specified, it simply
1587      * stores the unqualified package name on the heap...
1588      */
1589     package_name = strdup( pkg );
1590
1591   if( prev != NULL )
1592   {
1593     /* ...before ultimately advancing the reference pointer, to
1594      * link this item to the end of the actions list.
1595      */
1596     while( prev->next != NULL )
1597       prev = prev->next;
1598     prev->next = this;
1599   }
1600 }
1601
1602 pkgSetupAction::~pkgSetupAction()
1603 {
1604   /* Destructor for a single setup action item; it frees the heap
1605    * memory which was originally allocated to store the name of the
1606    * package with which the action was associated, and detaches the
1607    * item from the actions list, before it is deleted.
1608    */
1609   free( (void *)(package_name) );
1610   if( prev != NULL ) prev->next = next;
1611   if( next != NULL ) next->prev = prev;
1612 }
1613
1614 int APIENTRY WinMain
1615 ( HINSTANCE Instance, HINSTANCE PrevInstance, char *CmdLine, int ShowMode )
1616 {
1617   /* The program entry point for the mingw-get setup tool.
1618    */
1619   try
1620   { /* We allow only one instance of mingw-get to run at any time,
1621      * so first, we check to ensure that there is no other instance
1622      * already running.
1623      */
1624     if( ! WTK::RaiseAppWindow( Instance, ID_MAIN_WINDOW_CLASS ) )
1625       /*
1626        * There is no running mingw-get instance; we may tentatively
1627        * continue with the setup procedure.
1628        */
1629       SetupTool Install( Instance );
1630
1631     /* In any event, we return the setup tool status, as
1632      * the program exit code.
1633      */
1634     return SetupTool::Status;
1635   }
1636   catch( dmh_exception &e )
1637   {
1638     /* Here, we handle any fatal exception which has been raised
1639      * and identified by the diagnostic message handler...
1640      */
1641     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
1642     return EXIT_FAILURE;
1643   }
1644   catch( WTK::runtime_error &e )
1645   {
1646     /* ...while here, we diagnose any other error which was captured
1647      * during the creation of the application's window hierarchy, or
1648      * processing of its message loop...
1649      */
1650     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
1651     return EXIT_FAILURE;
1652   }
1653   catch(...)
1654   { /* ...and here, we diagnose any other error which we weren't
1655      * able to explicitly identify.
1656      */
1657     MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
1658     return EXIT_FAILURE;
1659   }
1660 }
1661
1662 /* $RCSfile$: end of file */