| /* |
| * Implementation of the Microsoft Installer (msi.dll) |
| * |
| * Copyright 2005 Aric Stewart for CodeWeavers |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include <stdarg.h> |
| #include <stdio.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "winerror.h" |
| #include "winreg.h" |
| #include "msi.h" |
| #include "msipriv.h" |
| |
| #include "wine/debug.h" |
| #include "wine/unicode.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(msi); |
| |
| typedef UINT (*EVENTHANDLER)(MSIPACKAGE*,LPCWSTR,msi_dialog *); |
| |
| struct _events { |
| LPCSTR event; |
| EVENTHANDLER handler; |
| }; |
| |
| struct subscriber { |
| struct list entry; |
| msi_dialog *dialog; |
| LPWSTR event; |
| LPWSTR control; |
| LPWSTR attribute; |
| }; |
| |
| static UINT ControlEvent_HandleControlEvent(MSIPACKAGE *, LPCWSTR, LPCWSTR, msi_dialog*); |
| |
| /* |
| * Create a dialog box and run it if it's modal |
| */ |
| static UINT event_do_dialog( MSIPACKAGE *package, LPCWSTR name, msi_dialog *parent, BOOL destroy_modeless ) |
| { |
| msi_dialog *dialog; |
| UINT r; |
| |
| /* create a new dialog */ |
| dialog = msi_dialog_create( package, name, parent, |
| ControlEvent_HandleControlEvent ); |
| if( dialog ) |
| { |
| /* kill the current modeless dialog */ |
| if( destroy_modeless && package->dialog ) |
| { |
| msi_dialog_destroy( package->dialog ); |
| package->dialog = NULL; |
| } |
| |
| /* modeless dialogs return an error message */ |
| r = msi_dialog_run_message_loop( dialog ); |
| if( r == ERROR_SUCCESS ) |
| msi_dialog_destroy( dialog ); |
| else |
| package->dialog = dialog; |
| } |
| else |
| r = ERROR_FUNCTION_FAILED; |
| |
| return r; |
| } |
| |
| |
| /* |
| * End a modal dialog box |
| */ |
| static UINT ControlEvent_EndDialog(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| static const WCHAR szExit[] = { |
| 'E','x','i','t',0}; |
| static const WCHAR szRetry[] = { |
| 'R','e','t','r','y',0}; |
| static const WCHAR szIgnore[] = { |
| 'I','g','n','o','r','e',0}; |
| static const WCHAR szReturn[] = { |
| 'R','e','t','u','r','n',0}; |
| |
| if (lstrcmpW(argument,szExit)==0) |
| package->CurrentInstallState = ERROR_INSTALL_USEREXIT; |
| else if (lstrcmpW(argument, szRetry) == 0) |
| package->CurrentInstallState = ERROR_INSTALL_SUSPEND; |
| else if (lstrcmpW(argument, szIgnore) == 0) |
| package->CurrentInstallState = ERROR_SUCCESS; |
| else if (lstrcmpW(argument, szReturn) == 0) |
| { |
| msi_dialog *parent = msi_dialog_get_parent(dialog); |
| msi_free(package->next_dialog); |
| package->next_dialog = (parent) ? strdupW(msi_dialog_get_name(parent)) : NULL; |
| package->CurrentInstallState = ERROR_SUCCESS; |
| } |
| else |
| { |
| ERR("Unknown argument string %s\n",debugstr_w(argument)); |
| package->CurrentInstallState = ERROR_FUNCTION_FAILED; |
| } |
| |
| ControlEvent_CleanupDialogSubscriptions(package, msi_dialog_get_name( dialog )); |
| msi_dialog_end_dialog( dialog ); |
| return ERROR_SUCCESS; |
| } |
| |
| /* |
| * transition from one modal dialog to another modal dialog |
| */ |
| static UINT ControlEvent_NewDialog(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog *dialog) |
| { |
| /* store the name of the next dialog, and signal this one to end */ |
| package->next_dialog = strdupW(argument); |
| ControlEvent_CleanupSubscriptions(package); |
| msi_dialog_end_dialog( dialog ); |
| return ERROR_SUCCESS; |
| } |
| |
| /* |
| * Create a new child dialog of an existing modal dialog |
| */ |
| static UINT ControlEvent_SpawnDialog(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog *dialog) |
| { |
| /* don't destroy a modeless dialogs that might be our parent */ |
| event_do_dialog( package, argument, dialog, FALSE ); |
| if( package->CurrentInstallState != ERROR_SUCCESS ) |
| msi_dialog_end_dialog( dialog ); |
| return ERROR_SUCCESS; |
| } |
| |
| /* |
| * Creates a dialog that remains up for a period of time |
| * based on a condition |
| */ |
| static UINT ControlEvent_SpawnWaitDialog(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| FIXME("Doing Nothing\n"); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT ControlEvent_DoAction(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| ACTION_PerformAction(package,argument,-1,TRUE); |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT ControlEvent_AddLocal(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| MSIFEATURE *feature = NULL; |
| |
| if (lstrcmpW(szAll,argument)) |
| { |
| MSI_SetFeatureStateW(package,argument,INSTALLSTATE_LOCAL); |
| } |
| else |
| { |
| LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) |
| msi_feature_set_state(package, feature, INSTALLSTATE_LOCAL); |
| |
| ACTION_UpdateComponentStates(package,argument); |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT ControlEvent_Remove(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| MSIFEATURE *feature = NULL; |
| |
| if (lstrcmpW(szAll,argument)) |
| { |
| MSI_SetFeatureStateW(package,argument,INSTALLSTATE_ABSENT); |
| } |
| else |
| { |
| LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) |
| msi_feature_set_state(package, feature, INSTALLSTATE_ABSENT); |
| |
| ACTION_UpdateComponentStates(package,argument); |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT ControlEvent_AddSource(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| MSIFEATURE *feature = NULL; |
| |
| if (lstrcmpW(szAll,argument)) |
| { |
| MSI_SetFeatureStateW(package,argument,INSTALLSTATE_SOURCE); |
| } |
| else |
| { |
| LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) |
| msi_feature_set_state(package, feature, INSTALLSTATE_SOURCE); |
| ACTION_UpdateComponentStates(package,argument); |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| static UINT ControlEvent_SetTargetPath(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| LPWSTR path = msi_dup_property( package->db, argument ); |
| MSIRECORD *rec = MSI_CreateRecord( 1 ); |
| UINT r; |
| |
| static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0}; |
| |
| MSI_RecordSetStringW( rec, 1, path ); |
| ControlEvent_FireSubscribedEvent( package, szSelectionPath, rec ); |
| |
| /* failure to set the path halts the executing of control events */ |
| r = MSI_SetTargetPathW(package, argument, path); |
| msi_free(path); |
| msi_free(&rec->hdr); |
| return r; |
| } |
| |
| static UINT ControlEvent_Reset(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| msi_dialog_reset(dialog); |
| return ERROR_SUCCESS; |
| } |
| |
| /* |
| * Subscribed events |
| */ |
| static void free_subscriber( struct subscriber *sub ) |
| { |
| msi_free(sub->event); |
| msi_free(sub->control); |
| msi_free(sub->attribute); |
| msi_free(sub); |
| } |
| |
| VOID ControlEvent_SubscribeToEvent( MSIPACKAGE *package, msi_dialog *dialog, |
| LPCWSTR event, LPCWSTR control, LPCWSTR attribute ) |
| { |
| struct subscriber *sub; |
| |
| sub = msi_alloc(sizeof (*sub)); |
| if( !sub ) |
| return; |
| sub->dialog = dialog; |
| sub->event = strdupW(event); |
| sub->control = strdupW(control); |
| sub->attribute = strdupW(attribute); |
| list_add_tail( &package->subscriptions, &sub->entry ); |
| } |
| |
| VOID ControlEvent_FireSubscribedEvent( MSIPACKAGE *package, LPCWSTR event, |
| MSIRECORD *rec ) |
| { |
| struct subscriber *sub; |
| |
| TRACE("Firing Event %s\n",debugstr_w(event)); |
| |
| LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) |
| { |
| if (lstrcmpiW(sub->event, event)) |
| continue; |
| msi_dialog_handle_event( sub->dialog, sub->control, |
| sub->attribute, rec ); |
| } |
| } |
| |
| VOID ControlEvent_CleanupDialogSubscriptions(MSIPACKAGE *package, LPWSTR dialog) |
| { |
| struct list *i, *t; |
| struct subscriber *sub; |
| |
| LIST_FOR_EACH_SAFE( i, t, &package->subscriptions ) |
| { |
| sub = LIST_ENTRY( i, struct subscriber, entry ); |
| |
| if ( lstrcmpW( msi_dialog_get_name( sub->dialog ), dialog )) |
| continue; |
| |
| list_remove( &sub->entry ); |
| free_subscriber( sub ); |
| } |
| } |
| |
| VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package) |
| { |
| struct list *i, *t; |
| struct subscriber *sub; |
| |
| LIST_FOR_EACH_SAFE( i, t, &package->subscriptions ) |
| { |
| sub = LIST_ENTRY( i, struct subscriber, entry ); |
| |
| list_remove( &sub->entry ); |
| free_subscriber( sub ); |
| } |
| } |
| |
| /* |
| * ACTION_DialogBox() |
| * |
| * Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED |
| * if the given parameter is not a dialog box |
| */ |
| UINT ACTION_DialogBox( MSIPACKAGE* package, LPCWSTR szDialogName ) |
| { |
| UINT r = ERROR_SUCCESS; |
| |
| if( package->next_dialog ) |
| ERR("Already a next dialog... ignoring it\n"); |
| package->next_dialog = NULL; |
| |
| /* |
| * Dialogs are chained by filling in the next_dialog member |
| * of the package structure, then terminating the current dialog. |
| * The code below sees the next_dialog member set, and runs the |
| * next dialog. |
| * We fall out of the loop below if we come across a modeless |
| * dialog, as it returns ERROR_IO_PENDING when we try to run |
| * its message loop. |
| */ |
| r = event_do_dialog( package, szDialogName, NULL, TRUE ); |
| while( r == ERROR_SUCCESS && package->next_dialog ) |
| { |
| LPWSTR name = package->next_dialog; |
| |
| package->next_dialog = NULL; |
| r = event_do_dialog( package, name, NULL, TRUE ); |
| msi_free( name ); |
| } |
| |
| if( r == ERROR_IO_PENDING ) |
| r = ERROR_SUCCESS; |
| |
| return r; |
| } |
| |
| static UINT ControlEvent_SetInstallLevel(MSIPACKAGE* package, LPCWSTR argument, |
| msi_dialog* dialog) |
| { |
| int iInstallLevel = atolW(argument); |
| |
| TRACE("Setting install level: %i\n", iInstallLevel); |
| |
| return MSI_SetInstallLevel( package, iInstallLevel ); |
| } |
| |
| static UINT ControlEvent_DirectoryListUp(MSIPACKAGE *package, LPCWSTR argument, |
| msi_dialog *dialog) |
| { |
| return msi_dialog_directorylist_up( dialog ); |
| } |
| |
| static UINT ControlEvent_ReinstallMode(MSIPACKAGE *package, LPCWSTR argument, |
| msi_dialog *dialog) |
| { |
| return msi_set_property( package->db, szReinstallMode, argument ); |
| } |
| |
| static UINT ControlEvent_Reinstall( MSIPACKAGE *package, LPCWSTR argument, |
| msi_dialog *dialog ) |
| { |
| return msi_set_property( package->db, szReinstall, argument ); |
| } |
| |
| static UINT ControlEvent_ValidateProductID(MSIPACKAGE *package, LPCWSTR argument, |
| msi_dialog *dialog) |
| { |
| LPWSTR key, template; |
| UINT ret = ERROR_SUCCESS; |
| |
| template = msi_dup_property( package->db, szPIDTemplate ); |
| key = msi_dup_property( package->db, szPIDKEY ); |
| |
| if (key && template) |
| { |
| FIXME( "partial stub: template %s key %s\n", debugstr_w(template), debugstr_w(key) ); |
| ret = msi_set_property( package->db, szProductID, key ); |
| } |
| msi_free( template ); |
| msi_free( key ); |
| return ret; |
| } |
| |
| static const struct _events Events[] = { |
| { "EndDialog",ControlEvent_EndDialog }, |
| { "NewDialog",ControlEvent_NewDialog }, |
| { "SpawnDialog",ControlEvent_SpawnDialog }, |
| { "SpawnWaitDialog",ControlEvent_SpawnWaitDialog }, |
| { "DoAction",ControlEvent_DoAction }, |
| { "AddLocal",ControlEvent_AddLocal }, |
| { "Remove",ControlEvent_Remove }, |
| { "AddSource",ControlEvent_AddSource }, |
| { "SetTargetPath",ControlEvent_SetTargetPath }, |
| { "Reset",ControlEvent_Reset }, |
| { "SetInstallLevel",ControlEvent_SetInstallLevel }, |
| { "DirectoryListUp",ControlEvent_DirectoryListUp }, |
| { "SelectionBrowse",ControlEvent_SpawnDialog }, |
| { "ReinstallMode",ControlEvent_ReinstallMode }, |
| { "Reinstall",ControlEvent_Reinstall }, |
| { "ValidateProductID",ControlEvent_ValidateProductID }, |
| { NULL,NULL }, |
| }; |
| |
| UINT ControlEvent_HandleControlEvent(MSIPACKAGE *package, LPCWSTR event, |
| LPCWSTR argument, msi_dialog* dialog) |
| { |
| int i = 0; |
| UINT rc = ERROR_SUCCESS; |
| |
| TRACE("Handling Control Event %s\n",debugstr_w(event)); |
| if (!event) |
| return rc; |
| |
| while( Events[i].event != NULL) |
| { |
| LPWSTR wevent = strdupAtoW(Events[i].event); |
| if (lstrcmpW(wevent,event)==0) |
| { |
| msi_free(wevent); |
| rc = Events[i].handler(package,argument,dialog); |
| return rc; |
| } |
| msi_free(wevent); |
| i++; |
| } |
| FIXME("unhandled control event %s arg(%s)\n", |
| debugstr_w(event), debugstr_w(argument)); |
| return rc; |
| } |