msi/tests: Cleanup registry after test.
[wine] / dlls / msi / events.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22 #include <stdio.h>
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winerror.h"
27 #include "winreg.h"
28 #include "msi.h"
29 #include "msipriv.h"
30
31 #include "wine/debug.h"
32 #include "wine/unicode.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(msi);
35
36 typedef UINT (*EVENTHANDLER)(MSIPACKAGE*,LPCWSTR,msi_dialog *);
37
38 struct _events {
39     LPCSTR event;
40     EVENTHANDLER handler;
41 };
42
43 struct subscriber {
44     struct list entry;
45     msi_dialog *dialog;
46     LPWSTR event;
47     LPWSTR control;
48     LPWSTR attribute;
49 };
50
51 static UINT ControlEvent_HandleControlEvent(MSIPACKAGE *, LPCWSTR, LPCWSTR, msi_dialog*);
52
53 /*
54  * Create a dialog box and run it if it's modal
55  */
56 static UINT event_do_dialog( MSIPACKAGE *package, LPCWSTR name, msi_dialog *parent, BOOL destroy_modeless )
57 {
58     msi_dialog *dialog;
59     UINT r;
60
61     /* create a new dialog */
62     dialog = msi_dialog_create( package, name, parent,
63                                 ControlEvent_HandleControlEvent );
64     if( dialog )
65     {
66         /* kill the current modeless dialog */
67         if( destroy_modeless && package->dialog )
68         {
69             msi_dialog_destroy( package->dialog );
70             package->dialog = NULL;
71         }
72
73         /* modeless dialogs return an error message */
74         r = msi_dialog_run_message_loop( dialog );
75         if( r == ERROR_SUCCESS )
76             msi_dialog_destroy( dialog );
77         else
78             package->dialog = dialog;
79     }
80     else
81         r = ERROR_FUNCTION_FAILED;
82
83     return r;
84 }
85
86
87 /*
88  * End a modal dialog box
89  */
90 static UINT ControlEvent_EndDialog(MSIPACKAGE* package, LPCWSTR argument, 
91                                    msi_dialog* dialog)
92 {
93     static const WCHAR szExit[] = {
94     'E','x','i','t',0};
95     static const WCHAR szRetry[] = {
96     'R','e','t','r','y',0};
97     static const WCHAR szIgnore[] = {
98     'I','g','n','o','r','e',0};
99     static const WCHAR szReturn[] = {
100     'R','e','t','u','r','n',0};
101
102     if (lstrcmpW(argument,szExit)==0)
103         package->CurrentInstallState = ERROR_INSTALL_USEREXIT;
104     else if (lstrcmpW(argument, szRetry) == 0)
105         package->CurrentInstallState = ERROR_INSTALL_SUSPEND;
106     else if (lstrcmpW(argument, szIgnore) == 0)
107         package->CurrentInstallState = -1;
108     else if (lstrcmpW(argument, szReturn) == 0)
109     {
110         msi_dialog *parent = msi_dialog_get_parent(dialog);
111         msi_free(package->next_dialog);
112         package->next_dialog = (parent) ? strdupW(msi_dialog_get_name(parent)) : NULL;
113         package->CurrentInstallState = ERROR_SUCCESS;
114     }
115     else
116     {
117         ERR("Unknown argument string %s\n",debugstr_w(argument));
118         package->CurrentInstallState = ERROR_FUNCTION_FAILED;
119     }
120
121     ControlEvent_CleanupDialogSubscriptions(package, msi_dialog_get_name( dialog ));
122     msi_dialog_end_dialog( dialog );
123     return ERROR_SUCCESS;
124 }
125
126 /*
127  * transition from one modal dialog to another modal dialog
128  */
129 static UINT ControlEvent_NewDialog(MSIPACKAGE* package, LPCWSTR argument, 
130                                    msi_dialog *dialog)
131 {
132     /* store the name of the next dialog, and signal this one to end */
133     package->next_dialog = strdupW(argument);
134     ControlEvent_CleanupSubscriptions(package);
135     msi_dialog_end_dialog( dialog );
136     return ERROR_SUCCESS;
137 }
138
139 /*
140  * Create a new child dialog of an existing modal dialog
141  */
142 static UINT ControlEvent_SpawnDialog(MSIPACKAGE* package, LPCWSTR argument, 
143                               msi_dialog *dialog)
144 {
145     /* don't destroy a modeless dialogs that might be our parent */
146     event_do_dialog( package, argument, dialog, FALSE );
147     if( package->CurrentInstallState != ERROR_SUCCESS )
148         msi_dialog_end_dialog( dialog );
149     return ERROR_SUCCESS;
150 }
151
152 /*
153  * Creates a dialog that remains up for a period of time
154  * based on a condition
155  */
156 static UINT ControlEvent_SpawnWaitDialog(MSIPACKAGE* package, LPCWSTR argument, 
157                                   msi_dialog* dialog)
158 {
159     FIXME("Doing Nothing\n");
160     return ERROR_SUCCESS;
161 }
162
163 static UINT ControlEvent_DoAction(MSIPACKAGE* package, LPCWSTR argument, 
164                                   msi_dialog* dialog)
165 {
166     ACTION_PerformAction(package,argument,-1,TRUE);
167     return ERROR_SUCCESS;
168 }
169
170 static UINT ControlEvent_AddLocal(MSIPACKAGE* package, LPCWSTR argument, 
171                                   msi_dialog* dialog)
172 {
173     static const WCHAR szAll[] = {'A','L','L',0};
174     MSIFEATURE *feature = NULL;
175
176     if (lstrcmpW(szAll,argument))
177     {
178         MSI_SetFeatureStateW(package,argument,INSTALLSTATE_LOCAL);
179     }
180     else
181     {
182         LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
183             msi_feature_set_state( feature, INSTALLSTATE_LOCAL );
184
185         ACTION_UpdateComponentStates(package,argument);
186     }
187     return ERROR_SUCCESS;
188 }
189
190 static UINT ControlEvent_Remove(MSIPACKAGE* package, LPCWSTR argument, 
191                                 msi_dialog* dialog)
192 {
193     static const WCHAR szAll[] = {'A','L','L',0};
194     MSIFEATURE *feature = NULL;
195
196     if (lstrcmpW(szAll,argument))
197     {
198         MSI_SetFeatureStateW(package,argument,INSTALLSTATE_ABSENT);
199     }
200     else
201     {
202         LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
203             msi_feature_set_state( feature, INSTALLSTATE_ABSENT );
204
205         ACTION_UpdateComponentStates(package,argument);
206     }
207     return ERROR_SUCCESS;
208 }
209
210 static UINT ControlEvent_AddSource(MSIPACKAGE* package, LPCWSTR argument, 
211                                    msi_dialog* dialog)
212 {
213     static const WCHAR szAll[] = {'A','L','L',0};
214     MSIFEATURE *feature = NULL;
215
216     if (lstrcmpW(szAll,argument))
217     {
218         MSI_SetFeatureStateW(package,argument,INSTALLSTATE_SOURCE);
219     }
220     else
221     {
222         LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
223             msi_feature_set_state( feature, INSTALLSTATE_SOURCE );
224         ACTION_UpdateComponentStates(package,argument);
225     }
226     return ERROR_SUCCESS;
227 }
228
229 static UINT ControlEvent_SetTargetPath(MSIPACKAGE* package, LPCWSTR argument, 
230                                    msi_dialog* dialog)
231 {
232     LPWSTR path = msi_dup_property( package, argument );
233     MSIRECORD *rec = MSI_CreateRecord( 1 );
234     UINT r;
235
236     static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
237
238     MSI_RecordSetStringW( rec, 1, path );
239     ControlEvent_FireSubscribedEvent( package, szSelectionPath, rec );
240
241     /* failure to set the path halts the executing of control events */
242     r = MSI_SetTargetPathW(package, argument, path);
243     msi_free(path);
244     msi_free(&rec->hdr);
245     return r;
246 }
247
248 static UINT ControlEvent_Reset(MSIPACKAGE* package, LPCWSTR argument, 
249                                    msi_dialog* dialog)
250 {
251     msi_dialog_reset(dialog);
252     return ERROR_SUCCESS;
253 }
254
255 /*
256  * Subscribed events
257  */
258 static void free_subscriber( struct subscriber *sub )
259 {
260     msi_free(sub->event);
261     msi_free(sub->control);
262     msi_free(sub->attribute);
263     msi_free(sub);
264 }
265
266 VOID ControlEvent_SubscribeToEvent( MSIPACKAGE *package, msi_dialog *dialog,
267                                     LPCWSTR event, LPCWSTR control, LPCWSTR attribute )
268 {
269     struct subscriber *sub;
270
271     sub = msi_alloc(sizeof (*sub));
272     if( !sub )
273         return;
274     sub->dialog = dialog;
275     sub->event = strdupW(event);
276     sub->control = strdupW(control);
277     sub->attribute = strdupW(attribute);
278     list_add_tail( &package->subscriptions, &sub->entry );
279 }
280
281 VOID ControlEvent_UnSubscribeToEvent( MSIPACKAGE *package, LPCWSTR event,
282                                       LPCWSTR control, LPCWSTR attribute )
283 {
284     struct list *i, *t;
285     struct subscriber *sub;
286
287     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
288     {
289         sub = LIST_ENTRY( i, struct subscriber, entry );
290
291         if( lstrcmpiW(sub->control,control) )
292             continue;
293         if( lstrcmpiW(sub->attribute,attribute) )
294             continue;
295         if( lstrcmpiW(sub->event,event) )
296             continue;
297         list_remove( &sub->entry );
298         free_subscriber( sub );
299     }
300 }
301
302 VOID ControlEvent_FireSubscribedEvent( MSIPACKAGE *package, LPCWSTR event, 
303                                        MSIRECORD *rec )
304 {
305     struct subscriber *sub;
306
307     TRACE("Firing Event %s\n",debugstr_w(event));
308
309     LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry )
310     {
311         if (lstrcmpiW(sub->event, event))
312             continue;
313         msi_dialog_handle_event( sub->dialog, sub->control,
314                                  sub->attribute, rec );
315     }
316 }
317
318 VOID ControlEvent_CleanupDialogSubscriptions(MSIPACKAGE *package, LPWSTR dialog)
319 {
320     struct list *i, *t;
321     struct subscriber *sub;
322
323     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
324     {
325         sub = LIST_ENTRY( i, struct subscriber, entry );
326
327         if ( lstrcmpW( msi_dialog_get_name( sub->dialog ), dialog ))
328             continue;
329
330         list_remove( &sub->entry );
331         free_subscriber( sub );
332     }
333 }
334
335 VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package)
336 {
337     struct list *i, *t;
338     struct subscriber *sub;
339
340     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
341     {
342         sub = LIST_ENTRY( i, struct subscriber, entry );
343
344         list_remove( &sub->entry );
345         free_subscriber( sub );
346     }
347 }
348
349 /*
350  * ACTION_DialogBox()
351  *
352  * Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED
353  * if the given parameter is not a dialog box
354  */
355 UINT ACTION_DialogBox( MSIPACKAGE* package, LPCWSTR szDialogName )
356 {
357     UINT r = ERROR_SUCCESS;
358
359     if( package->next_dialog )
360         ERR("Already a next dialog... ignoring it\n");
361     package->next_dialog = NULL;
362
363     /*
364      * Dialogs are chained by filling in the next_dialog member
365      *  of the package structure, then terminating the current dialog.
366      *  The code below sees the next_dialog member set, and runs the
367      *  next dialog.
368      * We fall out of the loop below if we come across a modeless
369      *  dialog, as it returns ERROR_IO_PENDING when we try to run
370      *  its message loop.
371      */
372     r = event_do_dialog( package, szDialogName, NULL, TRUE );
373     while( r == ERROR_SUCCESS && package->next_dialog )
374     {
375         LPWSTR name = package->next_dialog;
376
377         package->next_dialog = NULL;
378         r = event_do_dialog( package, name, NULL, TRUE );
379         msi_free( name );
380     }
381
382     if( r == ERROR_IO_PENDING )
383         r = ERROR_SUCCESS;
384
385     return r;
386 }
387
388 static UINT ControlEvent_SetInstallLevel(MSIPACKAGE* package, LPCWSTR argument,
389                                           msi_dialog* dialog)
390 {
391     int iInstallLevel = atolW(argument);
392
393     TRACE("Setting install level: %i\n", iInstallLevel);
394
395     return MSI_SetInstallLevel( package, iInstallLevel );
396 }
397
398 static UINT ControlEvent_DirectoryListUp(MSIPACKAGE *package, LPCWSTR argument,
399                                          msi_dialog *dialog)
400 {
401     return msi_dialog_directorylist_up( dialog );
402 }
403
404 static UINT ControlEvent_ReinstallMode(MSIPACKAGE *package, LPCWSTR argument,
405                                        msi_dialog *dialog)
406 {
407     static const WCHAR szReinstallMode[] = {'R','E','I','N','S','T','A','L','L','M','O','D','E',0};
408     return MSI_SetPropertyW( package, szReinstallMode, argument );
409 }
410
411 static const struct _events Events[] = {
412     { "EndDialog",ControlEvent_EndDialog },
413     { "NewDialog",ControlEvent_NewDialog },
414     { "SpawnDialog",ControlEvent_SpawnDialog },
415     { "SpawnWaitDialog",ControlEvent_SpawnWaitDialog },
416     { "DoAction",ControlEvent_DoAction },
417     { "AddLocal",ControlEvent_AddLocal },
418     { "Remove",ControlEvent_Remove },
419     { "AddSource",ControlEvent_AddSource },
420     { "SetTargetPath",ControlEvent_SetTargetPath },
421     { "Reset",ControlEvent_Reset },
422     { "SetInstallLevel",ControlEvent_SetInstallLevel },
423     { "DirectoryListUp",ControlEvent_DirectoryListUp },
424     { "SelectionBrowse",ControlEvent_SpawnDialog },
425     { "ReinstallMode",ControlEvent_ReinstallMode },
426     { NULL,NULL },
427 };
428
429 UINT ControlEvent_HandleControlEvent(MSIPACKAGE *package, LPCWSTR event,
430                                      LPCWSTR argument, msi_dialog* dialog)
431 {
432     int i = 0;
433     UINT rc = ERROR_SUCCESS;
434
435     TRACE("Handling Control Event %s\n",debugstr_w(event));
436     if (!event)
437         return rc;
438
439     while( Events[i].event != NULL)
440     {
441         LPWSTR wevent = strdupAtoW(Events[i].event);
442         if (lstrcmpW(wevent,event)==0)
443         {
444             msi_free(wevent);
445             rc = Events[i].handler(package,argument,dialog);
446             return rc;
447         }
448         msi_free(wevent);
449         i++;
450     }
451     FIXME("unhandled control event %s arg(%s)\n",
452           debugstr_w(event), debugstr_w(argument));
453     return rc;
454 }