shell32: Build language resource files separately.
[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 = ERROR_SUCCESS;
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(package, 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(package, 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(package, 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_FireSubscribedEvent( MSIPACKAGE *package, LPCWSTR event, 
282                                        MSIRECORD *rec )
283 {
284     struct subscriber *sub;
285
286     TRACE("Firing Event %s\n",debugstr_w(event));
287
288     LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry )
289     {
290         if (lstrcmpiW(sub->event, event))
291             continue;
292         msi_dialog_handle_event( sub->dialog, sub->control,
293                                  sub->attribute, rec );
294     }
295 }
296
297 VOID ControlEvent_CleanupDialogSubscriptions(MSIPACKAGE *package, LPWSTR dialog)
298 {
299     struct list *i, *t;
300     struct subscriber *sub;
301
302     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
303     {
304         sub = LIST_ENTRY( i, struct subscriber, entry );
305
306         if ( lstrcmpW( msi_dialog_get_name( sub->dialog ), dialog ))
307             continue;
308
309         list_remove( &sub->entry );
310         free_subscriber( sub );
311     }
312 }
313
314 VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package)
315 {
316     struct list *i, *t;
317     struct subscriber *sub;
318
319     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions )
320     {
321         sub = LIST_ENTRY( i, struct subscriber, entry );
322
323         list_remove( &sub->entry );
324         free_subscriber( sub );
325     }
326 }
327
328 /*
329  * ACTION_DialogBox()
330  *
331  * Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED
332  * if the given parameter is not a dialog box
333  */
334 UINT ACTION_DialogBox( MSIPACKAGE* package, LPCWSTR szDialogName )
335 {
336     UINT r = ERROR_SUCCESS;
337
338     if( package->next_dialog )
339         ERR("Already a next dialog... ignoring it\n");
340     package->next_dialog = NULL;
341
342     /*
343      * Dialogs are chained by filling in the next_dialog member
344      *  of the package structure, then terminating the current dialog.
345      *  The code below sees the next_dialog member set, and runs the
346      *  next dialog.
347      * We fall out of the loop below if we come across a modeless
348      *  dialog, as it returns ERROR_IO_PENDING when we try to run
349      *  its message loop.
350      */
351     r = event_do_dialog( package, szDialogName, NULL, TRUE );
352     while( r == ERROR_SUCCESS && package->next_dialog )
353     {
354         LPWSTR name = package->next_dialog;
355
356         package->next_dialog = NULL;
357         r = event_do_dialog( package, name, NULL, TRUE );
358         msi_free( name );
359     }
360
361     if( r == ERROR_IO_PENDING )
362         r = ERROR_SUCCESS;
363
364     return r;
365 }
366
367 static UINT ControlEvent_SetInstallLevel(MSIPACKAGE* package, LPCWSTR argument,
368                                           msi_dialog* dialog)
369 {
370     int iInstallLevel = atolW(argument);
371
372     TRACE("Setting install level: %i\n", iInstallLevel);
373
374     return MSI_SetInstallLevel( package, iInstallLevel );
375 }
376
377 static UINT ControlEvent_DirectoryListUp(MSIPACKAGE *package, LPCWSTR argument,
378                                          msi_dialog *dialog)
379 {
380     return msi_dialog_directorylist_up( dialog );
381 }
382
383 static UINT ControlEvent_ReinstallMode(MSIPACKAGE *package, LPCWSTR argument,
384                                        msi_dialog *dialog)
385 {
386     static const WCHAR szReinstallMode[] = {'R','E','I','N','S','T','A','L','L','M','O','D','E',0};
387     return MSI_SetPropertyW( package, szReinstallMode, argument );
388 }
389
390 static const struct _events Events[] = {
391     { "EndDialog",ControlEvent_EndDialog },
392     { "NewDialog",ControlEvent_NewDialog },
393     { "SpawnDialog",ControlEvent_SpawnDialog },
394     { "SpawnWaitDialog",ControlEvent_SpawnWaitDialog },
395     { "DoAction",ControlEvent_DoAction },
396     { "AddLocal",ControlEvent_AddLocal },
397     { "Remove",ControlEvent_Remove },
398     { "AddSource",ControlEvent_AddSource },
399     { "SetTargetPath",ControlEvent_SetTargetPath },
400     { "Reset",ControlEvent_Reset },
401     { "SetInstallLevel",ControlEvent_SetInstallLevel },
402     { "DirectoryListUp",ControlEvent_DirectoryListUp },
403     { "SelectionBrowse",ControlEvent_SpawnDialog },
404     { "ReinstallMode",ControlEvent_ReinstallMode },
405     { NULL,NULL },
406 };
407
408 UINT ControlEvent_HandleControlEvent(MSIPACKAGE *package, LPCWSTR event,
409                                      LPCWSTR argument, msi_dialog* dialog)
410 {
411     int i = 0;
412     UINT rc = ERROR_SUCCESS;
413
414     TRACE("Handling Control Event %s\n",debugstr_w(event));
415     if (!event)
416         return rc;
417
418     while( Events[i].event != NULL)
419     {
420         LPWSTR wevent = strdupAtoW(Events[i].event);
421         if (lstrcmpW(wevent,event)==0)
422         {
423             msi_free(wevent);
424             rc = Events[i].handler(package,argument,dialog);
425             return rc;
426         }
427         msi_free(wevent);
428         i++;
429     }
430     FIXME("unhandled control event %s arg(%s)\n",
431           debugstr_w(event), debugstr_w(argument));
432     return rc;
433 }