/* * 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 control_events { const WCHAR *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 (!strcmpW( argument, szExit )) package->CurrentInstallState = ERROR_INSTALL_USEREXIT; else if (!strcmpW( argument, szRetry )) package->CurrentInstallState = ERROR_INSTALL_SUSPEND; else if (!strcmpW( argument, szIgnore )) package->CurrentInstallState = ERROR_SUCCESS; else if (!strcmpW( argument, szReturn )) { 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, SCRIPT_NONE); return ERROR_SUCCESS; } static UINT ControlEvent_AddLocal( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) { MSIFEATURE *feature; LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) { if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) { if (feature->ActionRequest != INSTALLSTATE_LOCAL) msi_set_property( package->db, szPreselected, szOne, -1 ); MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_LOCAL ); } } return ERROR_SUCCESS; } static UINT ControlEvent_Remove( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) { MSIFEATURE *feature; LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) { if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) { if (feature->ActionRequest != INSTALLSTATE_ABSENT) msi_set_property( package->db, szPreselected, szOne, -1 ); MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_ABSENT ); } } return ERROR_SUCCESS; } static UINT ControlEvent_AddSource( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) { MSIFEATURE *feature; LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) { if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) { if (feature->ActionRequest != INSTALLSTATE_SOURCE) msi_set_property( package->db, szPreselected, szOne, -1 ); MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_SOURCE ); } } return ERROR_SUCCESS; } static UINT ControlEvent_SetTargetPath(MSIPACKAGE* package, LPCWSTR argument, msi_dialog* dialog) { static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0}; LPWSTR path = msi_dup_property( package->db, argument ); MSIRECORD *rec = MSI_CreateRecord( 1 ); UINT r = ERROR_SUCCESS; MSI_RecordSetStringW( rec, 1, path ); ControlEvent_FireSubscribedEvent( package, szSelectionPath, rec ); if (path) { /* 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; TRACE("event %s control %s attribute %s\n", debugstr_w(event), debugstr_w(control), debugstr_w(attribute)); LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) { if (!strcmpiW( sub->event, event ) && !strcmpiW( sub->control, control ) && !strcmpiW( sub->attribute, attribute )) { TRACE("already subscribed\n"); return; }; } if (!(sub = msi_alloc( sizeof(*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 (strcmpiW( 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 (strcmpW( 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, -1 ); } static UINT ControlEvent_Reinstall( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) { return msi_set_property( package->db, szReinstall, argument, -1 ); } static UINT ControlEvent_ValidateProductID(MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog) { return msi_validate_product_id( package ); } static const WCHAR end_dialogW[] = {'E','n','d','D','i','a','l','o','g',0}; static const WCHAR new_dialogW[] = {'N','e','w','D','i','a','l','o','g',0}; static const WCHAR spawn_dialogW[] = {'S','p','a','w','n','D','i','a','l','o','g',0}; static const WCHAR spawn_wait_dialogW[] = {'S','p','a','w','n','W','a','i','t','D','i','a','l','o','g',0}; static const WCHAR do_actionW[] = {'D','o','A','c','t','i','o','n',0}; static const WCHAR add_localW[] = {'A','d','d','L','o','c','a','l',0}; static const WCHAR removeW[] = {'R','e','m','o','v','e',0}; static const WCHAR add_sourceW[] = {'A','d','d','S','o','u','r','c','e',0}; static const WCHAR set_target_pathW[] = {'S','e','t','T','a','r','g','e','t','P','a','t','h',0}; static const WCHAR resetW[] = {'R','e','s','e','t',0}; static const WCHAR set_install_levelW[] = {'S','e','t','I','n','s','t','a','l','l','L','e','v','e','l',0}; static const WCHAR directory_list_upW[] = {'D','i','r','e','c','t','o','r','y','L','i','s','t','U','p',0}; static const WCHAR selection_browseW[] = {'S','e','l','e','c','t','i','o','n','B','r','o','w','s','e',0}; static const WCHAR reinstall_modeW[] = {'R','e','i','n','s','t','a','l','l','M','o','d','e',0}; static const WCHAR reinstallW[] = {'R','e','i','n','s','t','a','l','l',0}; static const WCHAR validate_product_idW[] = {'V','a','l','i','d','a','t','e','P','r','o','d','u','c','t','I','D',0}; static const struct control_events control_events[] = { { end_dialogW, ControlEvent_EndDialog }, { new_dialogW, ControlEvent_NewDialog }, { spawn_dialogW, ControlEvent_SpawnDialog }, { spawn_wait_dialogW, ControlEvent_SpawnWaitDialog }, { do_actionW, ControlEvent_DoAction }, { add_localW, ControlEvent_AddLocal }, { removeW, ControlEvent_Remove }, { add_sourceW, ControlEvent_AddSource }, { set_target_pathW, ControlEvent_SetTargetPath }, { resetW, ControlEvent_Reset }, { set_install_levelW, ControlEvent_SetInstallLevel }, { directory_list_upW, ControlEvent_DirectoryListUp }, { selection_browseW, ControlEvent_SpawnDialog }, { reinstall_modeW, ControlEvent_ReinstallMode }, { reinstallW, ControlEvent_Reinstall }, { validate_product_idW, ControlEvent_ValidateProductID }, { NULL, NULL } }; UINT ControlEvent_HandleControlEvent( MSIPACKAGE *package, LPCWSTR event, LPCWSTR argument, msi_dialog *dialog ) { unsigned int i; TRACE("handling control event %s\n", debugstr_w(event)); if (!event) return ERROR_SUCCESS; for (i = 0; control_events[i].event; i++) { if (!strcmpW( control_events[i].event, event )) return control_events[i].handler( package, argument, dialog ); } FIXME("unhandled control event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument)); return ERROR_SUCCESS; }