msi: Add the column type INTEGER as an alias for INT.
[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 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_CleanupSubscriptions(package);
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         {
189             feature->ActionRequest = INSTALLSTATE_LOCAL;
190             feature->Action = INSTALLSTATE_LOCAL;
191         }
192         ACTION_UpdateComponentStates(package,argument);
193     }
194     return ERROR_SUCCESS;
195 }
196
197 static UINT ControlEvent_Remove(MSIPACKAGE* package, LPCWSTR argument, 
198                                 msi_dialog* dialog)
199 {
200     static const WCHAR szAll[] = {'A','L','L',0};
201     MSIFEATURE *feature = NULL;
202
203     if (lstrcmpW(szAll,argument))
204     {
205         MSI_SetFeatureStateW(package,argument,INSTALLSTATE_ABSENT);
206     }
207     else
208     {
209         LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
210         {
211             feature->ActionRequest = INSTALLSTATE_ABSENT;
212             feature->Action= INSTALLSTATE_ABSENT;
213         }
214         ACTION_UpdateComponentStates(package,argument);
215     }
216     return ERROR_SUCCESS;
217 }
218
219 static UINT ControlEvent_AddSource(MSIPACKAGE* package, LPCWSTR argument, 
220                                    msi_dialog* dialog)
221 {
222     static const WCHAR szAll[] = {'A','L','L',0};
223     MSIFEATURE *feature = NULL;
224
225     if (lstrcmpW(szAll,argument))
226     {
227         MSI_SetFeatureStateW(package,argument,INSTALLSTATE_SOURCE);
228     }
229     else
230     {
231         LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
232         {
233             feature->ActionRequest = INSTALLSTATE_SOURCE;
234             feature->Action = INSTALLSTATE_SOURCE;
235         }
236         ACTION_UpdateComponentStates(package,argument);
237     }
238     return ERROR_SUCCESS;
239 }
240
241 static UINT ControlEvent_SetTargetPath(MSIPACKAGE* package, LPCWSTR argument, 
242                                    msi_dialog* dialog)
243 {
244     LPWSTR path = msi_dup_property( package, argument );
245     MSIRECORD *rec = MSI_CreateRecord( 1 );
246     UINT r;
247
248     static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0};
249
250     MSI_RecordSetStringW( rec, 1, path );
251     ControlEvent_FireSubscribedEvent( package, szSelectionPath, rec );
252
253     /* failure to set the path halts the executing of control events */
254     r = MSI_SetTargetPathW(package, argument, path);
255     msi_free(path);
256     msi_free(&rec->hdr);
257     return r;
258 }
259
260 static UINT ControlEvent_Reset(MSIPACKAGE* package, LPCWSTR argument, 
261                                    msi_dialog* dialog)
262 {
263     msi_dialog_reset(dialog);
264     return ERROR_SUCCESS;
265 }
266
267 /*
268  * Subscribed events
269  */
270 static void free_subscriber( struct subscriber *sub )
271 {
272     msi_free(sub->event);
273     msi_free(sub->control);
274     msi_free(sub->attribute);
275     msi_free(sub);
276 }
277
278 VOID ControlEvent_SubscribeToEvent( MSIPACKAGE *package, msi_dialog *dialog,
279                                     LPCWSTR event, LPCWSTR control, LPCWSTR attribute )
280 {
281     struct subscriber *sub;
282
283     sub = msi_alloc(sizeof (*sub));
284     if( !sub )
285         return;
286     sub->dialog = dialog;
287     sub->event = strdupW(event);
288     sub->control = strdupW(control);
289     sub->attribute = strdupW(attribute);
290     list_add_tail( &package->subscriptions, &sub->entry );
291 }
292
293 VOID ControlEvent_UnSubscribeToEvent( MSIPACKAGE *package, LPCWSTR event,
294                                       LPCWSTR control, LPCWSTR attribute )
295 {
296     struct list *i, *t;
297     struct subscriber *sub;
298
299     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
300     {
301         sub = LIST_ENTRY( i, struct subscriber, entry );
302
303         if( lstrcmpiW(sub->control,control) )
304             continue;
305         if( lstrcmpiW(sub->attribute,attribute) )
306             continue;
307         if( lstrcmpiW(sub->event,event) )
308             continue;
309         list_remove( &sub->entry );
310         free_subscriber( sub );
311     }
312 }
313
314 VOID ControlEvent_FireSubscribedEvent( MSIPACKAGE *package, LPCWSTR event, 
315                                        MSIRECORD *rec )
316 {
317     struct subscriber *sub;
318
319     TRACE("Firing Event %s\n",debugstr_w(event));
320
321     LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry )
322     {
323         if (lstrcmpiW(sub->event, event))
324             continue;
325         msi_dialog_handle_event( sub->dialog, sub->control,
326                                  sub->attribute, rec );
327     }
328 }
329
330 VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package)
331 {
332     struct list *i, *t;
333     struct subscriber *sub;
334
335     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
336     {
337         sub = LIST_ENTRY( i, struct subscriber, entry );
338
339         list_remove( &sub->entry );
340         free_subscriber( sub );
341     }
342 }
343
344 /*
345  * ACTION_DialogBox()
346  *
347  * Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED
348  * if the given parameter is not a dialog box
349  */
350 UINT ACTION_DialogBox( MSIPACKAGE* package, LPCWSTR szDialogName )
351 {
352     UINT r = ERROR_SUCCESS;
353
354     if( package->next_dialog )
355         ERR("Already a next dialog... ignoring it\n");
356     package->next_dialog = NULL;
357
358     /*
359      * Dialogs are chained by filling in the next_dialog member
360      *  of the package structure, then terminating the current dialog.
361      *  The code below sees the next_dialog member set, and runs the
362      *  next dialog.
363      * We fall out of the loop below if we come across a modeless
364      *  dialog, as it returns ERROR_IO_PENDING when we try to run
365      *  its message loop.
366      */
367     r = event_do_dialog( package, szDialogName, NULL, TRUE );
368     while( r == ERROR_SUCCESS && package->next_dialog )
369     {
370         LPWSTR name = package->next_dialog;
371
372         package->next_dialog = NULL;
373         r = event_do_dialog( package, name, NULL, TRUE );
374         msi_free( name );
375     }
376
377     if( r == ERROR_IO_PENDING )
378         r = ERROR_SUCCESS;
379
380     return r;
381 }
382
383 static UINT ControlEvent_SetInstallLevel(MSIPACKAGE* package, LPCWSTR argument,
384                                           msi_dialog* dialog)
385 {
386     int iInstallLevel = atolW(argument);
387
388     TRACE("Setting install level: %i\n", iInstallLevel);
389
390     return MSI_SetInstallLevel( package, iInstallLevel );
391 }
392
393 static UINT ControlEvent_DirectoryListUp(MSIPACKAGE *package, LPCWSTR argument,
394                                          msi_dialog *dialog)
395 {
396     return msi_dialog_directorylist_up( dialog );
397 }
398
399 static const struct _events Events[] = {
400     { "EndDialog",ControlEvent_EndDialog },
401     { "NewDialog",ControlEvent_NewDialog },
402     { "SpawnDialog",ControlEvent_SpawnDialog },
403     { "SpawnWaitDialog",ControlEvent_SpawnWaitDialog },
404     { "DoAction",ControlEvent_DoAction },
405     { "AddLocal",ControlEvent_AddLocal },
406     { "Remove",ControlEvent_Remove },
407     { "AddSource",ControlEvent_AddSource },
408     { "SetTargetPath",ControlEvent_SetTargetPath },
409     { "Reset",ControlEvent_Reset },
410     { "SetInstallLevel",ControlEvent_SetInstallLevel },
411     { "DirectoryListUp",ControlEvent_DirectoryListUp },
412     { "SelectionBrowse",ControlEvent_SpawnDialog },
413     { NULL,NULL },
414 };
415
416 UINT ControlEvent_HandleControlEvent(MSIPACKAGE *package, LPCWSTR event,
417                                      LPCWSTR argument, msi_dialog* dialog)
418 {
419     int i = 0;
420     UINT rc = ERROR_SUCCESS;
421
422     TRACE("Handling Control Event %s\n",debugstr_w(event));
423     if (!event)
424         return rc;
425
426     while( Events[i].event != NULL)
427     {
428         LPWSTR wevent = strdupAtoW(Events[i].event);
429         if (lstrcmpW(wevent,event)==0)
430         {
431             msi_free(wevent);
432             rc = Events[i].handler(package,argument,dialog);
433             return rc;
434         }
435         msi_free(wevent);
436         i++;
437     }
438     FIXME("unhandled control event %s arg(%s)\n",
439           debugstr_w(event), debugstr_w(argument));
440     return rc;
441 }