OSDN Git Service

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