OSDN Git Service

Support group affiliation with component package granularity.
[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     inline 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     /* When the user has requested progression to package installation...
1055      */
1056     if( Status == EXIT_CONTINUE )
1057     {
1058       /* ...then delegate that to the embedded mingw-get plugin, before
1059        * reasserting successful completion status for the setup tool.
1060        */
1061       DispatchSetupHookRequest( SETUP_HOOK_RUN_INSTALLER, setup_dll() );
1062       Status = EXIT_SUCCESS;
1063     }
1064
1065     /* If the mingw-get-0.dll and mingw-get-setup-0.dll libraries
1066      * were successfully loaded, to complete the installation process,
1067      * then we must now unload them; we also have no further use for
1068      * mingw-get-setup-0.dll, so we may delete it.
1069      */
1070     if( hook_dll != NULL ){ FreeLibrary( hook_dll ); _wunlink( setup_dll() ); }
1071     if( base_dll != NULL ){ FreeLibrary( base_dll ); }
1072
1073     /* We're done with the COM subsystem; release it.
1074      */
1075     CoUninitialize();
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 ), "bin" );
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_BASE ), "gui" );
1194
1195   /* ...always installing the licence pack...
1196    */
1197   linked = new pkgSetupAction( linked, archive_class( PACKAGE_BASE ), "lic" );
1198
1199   /* ...and finishing up with the setup DLL and XML data packages.
1200    */
1201   linked = new pkgSetupAction( linked, archive_class( PACKAGE_DATA ), "dll" );
1202   linked = new pkgSetupAction( linked, archive_class( PACKAGE_DATA ), "xml" );
1203
1204   /* Download packages to the mingw-get package cache, (which we will
1205    * create, as a side effect of downloading, if necessary), and unpack
1206    * them in place, to establish the basic mingw-get installation.
1207    */
1208   if( InitiatePackageInstallation( AppWindow, list ) == IDOK )
1209     Status = EXIT_CONTINUE;
1210
1211   /* Finally, clean up and we are done.
1212    */
1213   list->ClearAllActions();
1214 }
1215
1216 #define SETUP_OPTION(M)  SetupTool::IsPref(SETUP_OPTION_##M)
1217 #define SETUP_ASSERT(M)  SETUP_OPTION(M) ? BST_CHECKED : BST_UNCHECKED
1218 #define SETUP_REVERT(M)  SETUP_OPTION(M) ? BST_UNCHECKED : BST_CHECKED
1219
1220 inline void SetupTool::ShowPref( HWND window, int id, int option )
1221 {
1222   /* Helper method to update the display of any check box control,
1223    * within the installation preferences dialogue box, to match the
1224    * state of its associated setting flag within the setup object.
1225    */
1226   SendMessage( GetDlgItem( window, id ), BM_SETCHECK, option, 0 );
1227 }
1228
1229 inline void SetupTool::StorePref( HWND ctrl, int id, int option )
1230 {
1231   /* Helper method to record any user specified change of state
1232    * in any check box control, within the installation preferences
1233    * dialogue box, to its associated flag within the setup object.
1234    */
1235   if( SendMessage( GetDlgItem( ctrl, id ), BM_GETCHECK, 0, 0 ) == BST_CHECKED )
1236     /*
1237      * The user selected the option associated with the check box;
1238      * set the associated flag to its "on" state.
1239      */
1240     prefs |= option;
1241
1242   else
1243     /* The user cleared the check box; set the associated flag to
1244      * its "off" state.
1245      */
1246     prefs &= ~option;
1247 }
1248
1249 void SetupTool::EnablePrefs( HWND dialogue, int state )
1250 {
1251   /* Helper used by the ConfigureInstallationPreferences() method,
1252    * to set the enabled state for those selection controls which are
1253    * only meaningful when a particular controlling selection, (which
1254    * is nominally the choice to install the GUI), is in effect.
1255    */
1256   if( IsPref( SETUP_OPTION_PRIVILEGED ) )
1257   {
1258     /* The "all users" installation option is supported only
1259      * if the setup tool is run with administrator privilege.
1260      */
1261     EnableWindow( GetDlgItem( dialogue, ID_SETUP_ALL_USERS ), state );
1262   }
1263   /* All other options are available to everyone.
1264    */
1265   EnableWindow( GetDlgItem( dialogue, ID_SETUP_ME_ONLY ), state );
1266   EnableWindow( GetDlgItem( dialogue, ID_SETUP_START_MENU ), state );
1267   EnableWindow( GetDlgItem( dialogue, ID_SETUP_DESKTOP ), state );
1268 }
1269
1270 inline void SetupTool::EnablePrefs( HWND dialogue, HWND ref )
1271 {
1272   /* Overload the preceding EnablePrefs() method, so we can deduce
1273    * the state parameter from a reference check-box control.
1274    */
1275   EnablePrefs( dialogue, SendMessage( ref, BM_GETCHECK, 0, 0 ) );
1276 }
1277
1278 inline HMODULE SetupTool::HaveWorkingInstallation( void )
1279 {
1280   /* Helper method to check for a prior installation, by attempting
1281    * to load its installed DLL component into our process context.
1282    */
1283   dll_name = approot_path( L"libexec\\mingw-get\\mingw-get-0.dll" );
1284   return LoadLibraryW( dll_name );
1285 }
1286
1287 /* Identify the path to the mingw-get GUI client program, relative
1288  * to the root of the mingw-get installation tree.
1289  */
1290 const wchar_t *SetupTool::gui_program = L"libexec\\mingw-get\\guimain.exe";
1291
1292 inline int SetupTool::RunInstalledProgram( const wchar_t *program )
1293 {
1294   /* Helper method to spawn an external process, into which a
1295    * specified program image is loaded; (typically this will be
1296    * the mingw-get program, either inherited from a previous run
1297    * of this setup tool, or a copy which we have just installed).
1298    */
1299   int status;
1300
1301   /* We assume that the program to run lives within the directory
1302    * heirarchy specified for mingw-get installation.
1303    */
1304   program = approot_path( program );
1305   if( (status = _wspawnl( _P_NOWAIT, program, program, NULL )) != -1 )
1306   {
1307     /* When the specified image has been successfully loaded, we
1308      * give it around 1500 ms to get running, then we promote it
1309      * to the foreground.
1310      */
1311     Sleep( 1500 ); WTK::RaiseAppWindow( NULL, ID_MAIN_WINDOW_CLASS );
1312   }
1313   /* The return value is that returned by the spawn request.
1314    */
1315   return status;
1316 }
1317
1318 static inline int MessageDialogue( HINSTANCE app, int id, HWND owner )
1319 {
1320   /* Helper function to emulate MessageBox behaviour using an
1321    * application specified dialogue box resource.
1322    */
1323   return DialogBox( app, MAKEINTRESOURCE( id ), owner, confirm );
1324 }
1325
1326 int CALLBACK SetupTool::ConfigureInstallationPreferences
1327 ( HWND window, unsigned int msg, WPARAM request, LPARAM data )
1328 {
1329   /* Handler for user interaction with the installation preferences
1330    * dialogue; configures options for the ensuing installation.
1331    */
1332   HMODULE dll_hook;
1333   switch( msg )
1334   {
1335     /* We need to handle only two classes of message...
1336      */
1337     case WM_INITDIALOG:
1338       /*
1339        * On initialisation of the dialogue, we ensure that the
1340        * displayed state of the controls is consistent with the
1341        * default, or previous settings, as specified within the
1342        * SetupTool object.
1343        */
1344       EnablePrefs( window, IsPref( SETUP_OPTION_WITH_GUI ) );
1345       ShowPref( window, ID_SETUP_WITH_GUI, SETUP_ASSERT( WITH_GUI ) );
1346       ShowPref( window, ID_SETUP_ME_ONLY, SETUP_REVERT( ALL_USERS ) );
1347       ShowPref( window, ID_SETUP_ALL_USERS, SETUP_ASSERT( ALL_USERS ) );
1348       ShowPref( window, ID_SETUP_START_MENU, SETUP_ASSERT( START_MENU ) );
1349       ShowPref( window, ID_SETUP_DESKTOP, SETUP_ASSERT( DESKTOP ) );
1350       Invoke()->ShowInstallationDirectory( window );
1351
1352       /* Explicitly assign focus to the "Continue" button, and
1353        * return FALSE so the default message handler will not
1354        * override this with any default choice.
1355        */
1356       SetFocus( GetDlgItem( window, IDOK ) );
1357       return FALSE;
1358
1359     case WM_COMMAND:
1360       switch( msg = LOWORD( request ) )
1361       {
1362         /* A small selection of WM_COMMAND class messages
1363          * requires our attention.
1364          */
1365         case ID_SETUP_WITH_GUI:
1366           if( HIWORD( request ) == BN_CLICKED )
1367             /*
1368              * The user has toggled the state of the GUI installation
1369              * option; we intercept this, so we may assign appropriate
1370              * availablity of other preferences...
1371              */
1372             EnablePrefs( window, (HWND)(data) );
1373
1374           /* ...but otherwise, we leave the default message handler to
1375            * process this in its default manner.
1376            */
1377           return FALSE;
1378
1379         case ID_SETUP_CHDIR:
1380           /* The user has selected the option to change the directory
1381            * into which mingw-get is to be installed; we handle this
1382            * request explicitly...
1383            */
1384           Invoke()->ChangeInstallationDirectory( window );
1385           /*
1386            * ...with no further demand on the default message handler.
1387            */
1388           return TRUE;
1389
1390         case IDOK:
1391           /* The user has clicked the "Continue" button, (presumably
1392            * after making an appropriate selection of his installation
1393            * preferences; before blindly proceeding to installation,
1394            * check for any previously existing DLL component...
1395            */
1396           if( (dll_hook = Invoke()->HaveWorkingInstallation()) != NULL )
1397           {
1398             /* ...and, when one exists and has been loaded into our
1399              * runtime context, unload it before offering a choice of
1400              * how to continue...
1401              */
1402             FreeLibrary( dll_hook );
1403             switch( msg = MessageDialogue( NULL, IDD_SETUP_EXISTS, window ) )
1404             {
1405               case IDRETRY:
1406                 /* The user has indicated a preference to continue
1407                  * installation, but with an alternative installation
1408                  * directory, so that the existing installation may
1409                  * be preserved; go back to change directory.
1410                  */
1411                 Invoke()->ChangeInstallationDirectory( window );
1412                 break;
1413
1414               case ID_SETUP_RUN:
1415                 /* The user has indicated a preference to switch to
1416                  * advanced installation mode, by running the existing
1417                  * copy of mingw-get; attempt to do so, before falling
1418                  * through to cancel this setup session.
1419                  */
1420                 Invoke()->RunInstalledProgram( gui_program );
1421
1422               case IDCANCEL:
1423                 /* The current setup session is to be cancelled; pass
1424                  * the cancellation request down the dialogue stack.
1425                  */
1426                 EndDialog( window, IDCANCEL );
1427                 return TRUE;
1428             }
1429           }
1430         case IDRETRY:
1431         case ID_SETUP_SHOW_LICENCE:
1432           /* we propagate his chosen options back to
1433            * the SetupTool object...
1434            */
1435           StorePref( window, ID_SETUP_DESKTOP, SETUP_OPTION_DESKTOP );
1436           StorePref( window, ID_SETUP_START_MENU, SETUP_OPTION_START_MENU );
1437           StorePref( window, ID_SETUP_ALL_USERS, SETUP_OPTION_ALL_USERS );
1438           StorePref( window, ID_SETUP_WITH_GUI, SETUP_OPTION_WITH_GUI );
1439           /*
1440            * ...before simply falling through...
1441            */
1442         case IDCANCEL:
1443           /* ...to accept the chosen preferences when the user clicked
1444            * "Continue", or to discard them on "Cancel"; in either case
1445            * we close the dialogue box, passing the closure method ID
1446            * back to the caller...
1447            */
1448           EndDialog( window, msg );
1449           /*
1450            * ...while informing the default message handler that we do
1451            * not require it to process this message.
1452            */
1453           return TRUE;
1454       }
1455   }
1456   /* Any other messages are simply ignored, leaving the default
1457    * message handler to process them.
1458    */
1459   return FALSE;
1460 }
1461
1462 inline void SetupTool::ShowInstallationDirectory( HWND dialogue )
1463 {
1464   /* Helper method to display the active choice of installation directory
1465    * within the preferences configuration dialogue.
1466    */
1467   HWND viewport;
1468   if( (viewport = GetDlgItem( dialogue, ID_SETUP_CURDIR )) != NULL )
1469     SendMessageW( viewport, WM_SETTEXT, 0, (LPARAM)(approot_path()) );
1470 }
1471
1472 inline int SetupTool::SetInstallationPreferences( HWND AppWindow )
1473 {
1474   /* Helper method to display a dialogue box, offering the user
1475    * a choice of installation preferences.
1476    */
1477   return DialogBox( NULL, MAKEINTRESOURCE( IDD_SETUP_OPTIONS ), AppWindow,
1478       ConfigureInstallationPreferences
1479     );
1480 }
1481
1482 inline int SetupTool::InstallationRequest( HWND AppWindow )
1483 {
1484   /* Method to process requests to proceed with installation, on user
1485    * demand from the opening dialogue box.
1486    *
1487    * First step is to garner installation preferences from the user.
1488    */
1489   int request;
1490   do { if( (request = SetInstallationPreferences( AppWindow )) == IDOK )
1491        {
1492          /* When the user is satisfied that all options have been
1493           * appropriately specified, apply them.
1494           */
1495          DoFirstTimeSetup( AppWindow );
1496          const WTK::StringResource description( NULL, ID_MAIN_WINDOW_CAPTION );
1497          if( IsPref( SETUP_OPTION_WITH_GUI ) )
1498            for( int i = 2; i < 6; i++ )
1499            {
1500              /* When the user has requested the creation of program
1501               * "shortcuts", create them as appropriate.
1502               */
1503              if( (IsPref( i ) | IsPref( SETUP_OPTION_ALL_USERS )) == i )
1504                CreateApplicationLauncher( i, gui_program, description,
1505                    (i & SETUP_OPTION_DESKTOP) ? "MinGW Installer" : description
1506                  );
1507            }
1508        }
1509        /* Continue collecting preference settings, until the user elects to
1510         * invoke the "continue" action.
1511         */
1512      } while( request == IDRETRY );
1513   return request;
1514 }
1515
1516 int CALLBACK SetupTool::OpeningDialogue
1517 ( HWND dlg, unsigned int msg, WPARAM request, LPARAM )
1518 {
1519   /* Procedure to manage the initial dialogue box, which serves as
1520    * the main window for the setup application.
1521    */
1522   switch( msg )
1523   { case WM_INITDIALOG:
1524       /* Invoked when the dialogue box is first displayed; centre it
1525        * on-screen, and assign an appropriate caption.
1526        */
1527       WTK::AlignWindow( dlg, WTK_ALIGN_CENTRED | WTK_ALIGN_ONSCREEN );
1528       ChangeCaption( dlg, WTK::StringResource( NULL, ID_MAIN_DIALOGUE_CAPTION ) );
1529       return TRUE;
1530
1531     case WM_COMMAND:
1532       /* Handle requests from user interaction with the controls which
1533        * are provided within the dialogue box.
1534        */
1535       switch( msg = LOWORD( request ) )
1536       {
1537         case IDOK:
1538           /* User has elected to proceed with installation; inhibit
1539            * further control interaction, and process the request.
1540            */
1541           EnableWindow( GetDlgItem( dlg, IDOK ), FALSE );
1542           EnableWindow( GetDlgItem( dlg, IDCANCEL ), FALSE );
1543           while( Invoke()->InstallationRequest( dlg ) == ID_SETUP_SHOW_LICENCE )
1544             /*
1545              * During installation, the user may request to view
1546              * the product licence; honour such requests.
1547              */
1548             BrowseLicenceDocument( dlg );
1549
1550         case IDCANCEL:
1551           /* Invoked immediately, when the user elects to cancel the
1552            * installation, otherwise by fall through on completion of
1553            * a request to proceed; this closes the top-level dialogue,
1554            * so terminating the application/
1555            */
1556           EndDialog( dlg, msg );
1557           return TRUE;
1558
1559         case ID_SETUP_SHOW_LICENCE:
1560           /* This represents a confirmation that the user would like
1561            * to view the licence document for the package.
1562            */
1563           BrowseLicenceDocument( dlg );
1564           return TRUE;
1565       }
1566   }
1567   /* Other messages to the opening dialogue box may be safely ignored.
1568    */
1569   return FALSE;
1570 }
1571
1572 pkgSetupAction::pkgSetupAction
1573 ( pkgSetupAction *queue, const char *pkg, const char *ext ):
1574 flags( ACTION_DOWNLOAD + ACTION_INSTALL ), prev( queue ), next( NULL )
1575 {
1576   /* Constructor for a setup action item; having initialised the
1577    * flags, and the link reference to the first entry, if any, in
1578    * the actions list...
1579    */
1580   if( ext != NULL )
1581   {
1582     /* ...it constructs the name of the package with which the action
1583      * is to be associated, qualified by the specified extension, and
1584      * copies it to the heap...
1585      */
1586     char ref[1 + snprintf( NULL, 0, pkg, ext )]; sprintf( ref, pkg, ext );
1587     package_name = strdup( ref );
1588   }
1589   else
1590     /* ...or, when no qualifying extension is specified, it simply
1591      * stores the unqualified package name on the heap...
1592      */
1593     package_name = strdup( pkg );
1594
1595   if( prev != NULL )
1596   {
1597     /* ...before ultimately advancing the reference pointer, to
1598      * link this item to the end of the actions list.
1599      */
1600     while( prev->next != NULL )
1601       prev = prev->next;
1602     prev->next = this;
1603   }
1604 }
1605
1606 pkgSetupAction::~pkgSetupAction()
1607 {
1608   /* Destructor for a single setup action item; it frees the heap
1609    * memory which was originally allocated to store the name of the
1610    * package with which the action was associated, and detaches the
1611    * item from the actions list, before it is deleted.
1612    */
1613   free( (void *)(package_name) );
1614   if( prev != NULL ) prev->next = next;
1615   if( next != NULL ) next->prev = prev;
1616 }
1617
1618 int APIENTRY WinMain
1619 ( HINSTANCE Instance, HINSTANCE PrevInstance, char *CmdLine, int ShowMode )
1620 {
1621   /* The program entry point for the mingw-get setup tool.
1622    */
1623   try
1624   { /* We allow only one instance of mingw-get to run at any time,
1625      * so first, we check to ensure that there is no other instance
1626      * already running.
1627      */
1628     if( ! WTK::RaiseAppWindow( Instance, ID_MAIN_WINDOW_CLASS ) )
1629       /*
1630        * There is no running mingw-get instance; we may tentatively
1631        * continue with the setup procedure.
1632        */
1633       SetupTool Install( Instance );
1634
1635     /* In any event, we return the setup tool status, as
1636      * the program exit code.
1637      */
1638     return SetupTool::Status;
1639   }
1640   catch( dmh_exception &e )
1641   {
1642     /* Here, we handle any fatal exception which has been raised
1643      * and identified by the diagnostic message handler...
1644      */
1645     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
1646     return EXIT_FAILURE;
1647   }
1648   catch( WTK::runtime_error &e )
1649   {
1650     /* ...while here, we diagnose any other error which was captured
1651      * during the creation of the application's window hierarchy, or
1652      * processing of its message loop...
1653      */
1654     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
1655     return EXIT_FAILURE;
1656   }
1657   catch(...)
1658   { /* ...and here, we diagnose any other error which we weren't
1659      * able to explicitly identify.
1660      */
1661     MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
1662     return EXIT_FAILURE;
1663   }
1664 }
1665
1666 /* $RCSfile$: end of file */