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