diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in index 8b0574901d2..702699d02e1 100644 --- a/dlls/msi/Makefile.in +++ b/dlls/msi/Makefile.in @@ -16,6 +16,7 @@ C_SRCS = \ delete.c \ dialog.c \ distinct.c \ + events.c \ format.c \ handle.c \ insert.c \ diff --git a/dlls/msi/action.c b/dlls/msi/action.c index 4ce3050ba74..81681bb8753 100644 --- a/dlls/msi/action.c +++ b/dlls/msi/action.c @@ -685,6 +685,21 @@ void ACTION_free_package_structures( MSIPACKAGE* package) HeapFree(GetProcessHeap(),0,package->CommitAction); HeapFree(GetProcessHeap(),0,package->PackagePath); + + /* cleanup control event subscriptions */ + ControlEvent_CleanupSubscriptions(package); +} + +static void ce_actiontext(MSIPACKAGE* package, LPCWSTR action) +{ + static const WCHAR szActionText[] = + {'A','c','t','i','o','n','T','e','x','t',0}; + MSIRECORD *row; + + row = MSI_CreateRecord(1); + MSI_RecordSetStringW(row,1,action); + ControlEvent_FireSubscribedEvent(package,szActionText, row); + msiobj_release(&row->hdr); } static void ui_progress(MSIPACKAGE *package, int a, int b, int c, int d ) @@ -714,6 +729,8 @@ static void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * recor MSIQUERY * view; MSIRECORD * row = 0; DWORD size; + static const WCHAR szActionData[] = + {'A','c','t','i','o','n','D','a','t','a',0}; if (!package->LastAction || strcmpW(package->LastAction,action)) { @@ -764,6 +781,9 @@ static void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * recor MSI_RecordSetStringW(row,1,message); MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row); + + ControlEvent_FireSubscribedEvent(package,szActionData, row); + msiobj_release(&row->hdr); } @@ -1403,6 +1423,7 @@ BOOL ACTION_HandleStandardAction(MSIPACKAGE *package, LPCWSTR action, UINT* rc) { if (strcmpW(StandardActions[i].action, action)==0) { + ce_actiontext(package, action); ui_actioninfo(package, action, TRUE, 0); ui_actionstart(package, action); if (StandardActions[i].handler) @@ -1427,15 +1448,11 @@ BOOL ACTION_HandleDialogBox(MSIPACKAGE *package, LPCWSTR dialog, UINT* rc) { BOOL ret = FALSE; - /* - * for the UI when we get that working - * if (ACTION_DialogBox(package,dialog) == ERROR_SUCCESS) { *rc = package->CurrentInstallState; ret = TRUE; } - */ return ret; } diff --git a/dlls/msi/action.h b/dlls/msi/action.h index 673178ec70d..0f8c4a688c5 100644 --- a/dlls/msi/action.h +++ b/dlls/msi/action.h @@ -196,3 +196,8 @@ int get_loaded_component(MSIPACKAGE* package, LPCWSTR Component ); int get_loaded_feature(MSIPACKAGE* package, LPCWSTR Feature ); int get_loaded_file(MSIPACKAGE* package, LPCWSTR file); int track_tempfile(MSIPACKAGE *package, LPCWSTR name, LPCWSTR path); + +/* control event stuff */ +VOID ControlEvent_FireSubscribedEvent(MSIPACKAGE *package, LPCWSTR event, + MSIRECORD *data); +VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package); diff --git a/dlls/msi/events.c b/dlls/msi/events.c new file mode 100644 index 00000000000..e65f64840ac --- /dev/null +++ b/dlls/msi/events.c @@ -0,0 +1,478 @@ +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* +http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/controlevent_overview.asp +*/ + +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "winerror.h" +#include "winreg.h" +#include "msi.h" +#include "msipriv.h" +#include "action.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(msi); + +typedef void (*EVENTHANDLER)(MSIPACKAGE*,LPCWSTR,msi_dialog *); + +struct _events { + LPSTR event; + EVENTHANDLER handler; +}; + +struct _subscriber { + LPWSTR control; + LPWSTR attribute; + struct _subscriber *next; +}; + +struct _subscription_chain { + LPWSTR event; + struct _subscriber *chain; +}; + +struct _subscriptions { + DWORD chain_count; + struct _subscription_chain* chain; +}; + + +VOID 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 *dialog; + UINT r; + + /* kill the current modeless dialog */ + if( package->dialog ) + msi_dialog_destroy( package->dialog ); + package->dialog = NULL; + + /* create a new dialog */ + dialog = msi_dialog_create( package, name, + ControlEvent_HandleControlEvent ); + if( dialog ) + { + /* 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 VOID 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 = -1; + else if (lstrcmpW(argument, szReturn) == 0) + package->CurrentInstallState = ERROR_SUCCESS; + else + { + ERR("Unknown argument string %s\n",debugstr_w(argument)); + package->CurrentInstallState = ERROR_FUNCTION_FAILED; + } + + msi_dialog_end_dialog( dialog ); +} + +/* + * transition from one modal dialog to another modal dialog + */ +static VOID 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); + msi_dialog_end_dialog( dialog ); +} + +/* + * Create a new child dialog of an existing modal dialog + */ +static VOID ControlEvent_SpawnDialog(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog *dialog) +{ + event_do_dialog( package, argument ); + if( package->CurrentInstallState != ERROR_SUCCESS ) + msi_dialog_end_dialog( dialog ); +} + +/* + * Creates a dialog that remains up for a period of time + * based on a condition + */ +static VOID ControlEvent_SpawnWaitDialog(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog* dialog) +{ + FIXME("Doing Nothing\n"); +} + +static VOID ControlEvent_DoAction(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog* dialog) +{ + ACTION_PerformAction(package,argument); +} + +static VOID ControlEvent_AddLocal(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog* dialog) +{ + static const WCHAR szAll[] = {'A','L','L',0}; + int i; + + if (lstrcmpW(szAll,argument)) + { + MSI_SetFeatureStateW(package,argument,INSTALLSTATE_LOCAL); + } + else + { + for (i = 0; i < package->loaded_features; i++) + { + package->features[i].ActionRequest = INSTALLSTATE_LOCAL; + package->features[i].Action = INSTALLSTATE_LOCAL; + } + ACTION_UpdateComponentStates(package,argument); + } + +} + +static VOID ControlEvent_Remove(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog* dialog) +{ + static const WCHAR szAll[] = {'A','L','L',0}; + int i; + + if (lstrcmpW(szAll,argument)) + { + MSI_SetFeatureStateW(package,argument,INSTALLSTATE_ABSENT); + } + else + { + for (i = 0; i < package->loaded_features; i++) + { + package->features[i].ActionRequest = INSTALLSTATE_ABSENT; + package->features[i].Action= INSTALLSTATE_ABSENT; + } + ACTION_UpdateComponentStates(package,argument); + } +} + +static VOID ControlEvent_AddSource(MSIPACKAGE* package, LPCWSTR argument, + msi_dialog* dialog) +{ + static const WCHAR szAll[] = {'A','L','L',0}; + int i; + + if (lstrcmpW(szAll,argument)) + { + MSI_SetFeatureStateW(package,argument,INSTALLSTATE_SOURCE); + } + else + { + for (i = 0; i < package->loaded_features; i++) + { + package->features[i].ActionRequest = INSTALLSTATE_SOURCE; + package->features[i].Action = INSTALLSTATE_SOURCE; + } + ACTION_UpdateComponentStates(package,argument); + } +} + + +/* + * Subscribed events + */ +static void free_subscriber(struct _subscriber *who) +{ + HeapFree(GetProcessHeap(),0,who->control); + HeapFree(GetProcessHeap(),0,who->attribute); + HeapFree(GetProcessHeap(),0,who); +} + +VOID ControlEvent_SubscribeToEvent(MSIPACKAGE *package, LPCWSTR event, + LPCWSTR control, LPCWSTR attribute) +{ + int i; + struct _subscription_chain *chain; + struct _subscriber *subscriber, *ptr; + + if (!package->EventSubscriptions) + { + package->EventSubscriptions = HeapAlloc(GetProcessHeap(), 0, + sizeof(struct _subscriptions)); + package->EventSubscriptions->chain_count = 0; + } + + chain = NULL; + + for (i = 0; i < package->EventSubscriptions->chain_count; i++) + { + if (lstrcmpiW(package->EventSubscriptions->chain[i].event,event)==0) + { + chain = &package->EventSubscriptions->chain[i]; + break; + } + } + + if (chain == NULL) + { + if (package->EventSubscriptions->chain_count) + chain = HeapReAlloc(GetProcessHeap(), 0, + package->EventSubscriptions->chain, + (package->EventSubscriptions->chain_count + 1) * + sizeof (struct _subscription_chain)); + else + chain= HeapAlloc(GetProcessHeap(),0, sizeof (struct + _subscription_chain)); + + package->EventSubscriptions->chain = chain; + chain = &package->EventSubscriptions->chain[ + package->EventSubscriptions->chain_count]; + package->EventSubscriptions->chain_count++; + memset(chain,0,sizeof(struct _subscription_chain)); + chain->event = strdupW(event); + } + + subscriber = ptr = chain->chain; + while (ptr) + { + subscriber = ptr; + ptr = ptr->next; + } + + ptr = HeapAlloc(GetProcessHeap(),0,sizeof(struct _subscriber)); + ptr->control = strdupW(control); + ptr->attribute = strdupW(attribute); + ptr->next = NULL; + + if (subscriber) + subscriber->next = ptr; + else + chain->chain = ptr; +} + +VOID ControlEvent_UnSubscribeToEvent(MSIPACKAGE *package, LPCWSTR event, + LPCWSTR control, LPCWSTR attribute) +{ + int i; + + if (!package->EventSubscriptions) + return; + + for (i = 0; i < package->EventSubscriptions->chain_count; i++) + { + if (lstrcmpiW(package->EventSubscriptions->chain[i].event,event)==0) + { + struct _subscriber *who; + struct _subscriber *prev=NULL; + who = package->EventSubscriptions->chain[i].chain; + while (who) + { + if (lstrcmpiW(who->control,control)==0 + && lstrcmpiW(who->attribute,attribute)==0) + { + if (prev) + prev->next = who->next; + else + package->EventSubscriptions->chain[i].chain = who->next; + + free_subscriber(who); + } + else + { + prev = who; + who = who->next; + } + } + break; + } + } +} + +VOID ControlEvent_FireSubscribedEvent(MSIPACKAGE *package, LPCWSTR event, + MSIRECORD *data) +{ + int i; + + TRACE("Firing Event %s\n",debugstr_w(event)); + + if (!package->dialog) + return; + + if (!package->EventSubscriptions) + return; + + for (i = 0; i < package->EventSubscriptions->chain_count; i++) + { + if (lstrcmpiW(package->EventSubscriptions->chain[i].event,event)==0) + { + struct _subscriber *who; + who = package->EventSubscriptions->chain[i].chain; + while (who) + { + ERR("Should Fire event for %s %s\n", + debugstr_w(who->control), debugstr_w(who->attribute)); + /* + msi_dialog_fire_subscribed_event(package->dialog, who->control, + who->attribute, data); + */ + who = who->next; + } + break; + } + } +} + +VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package) +{ + int i; + + if (!package->EventSubscriptions) + return; + + for (i = 0; i < package->EventSubscriptions->chain_count; i++) + { + struct _subscriber *who; + struct _subscriber *ptr; + who = package->EventSubscriptions->chain[i].chain; + while (who) + { + ptr = who; + who = who->next; + free_subscriber(ptr); + } + HeapFree(GetProcessHeap(), 0, + package->EventSubscriptions->chain[i].event); + } + HeapFree(GetProcessHeap(),0,package->EventSubscriptions->chain); + HeapFree(GetProcessHeap(),0,package->EventSubscriptions); + package->EventSubscriptions = NULL; +} + +/* + * 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 ); + while( r == ERROR_SUCCESS && package->next_dialog ) + { + LPWSTR name = package->next_dialog; + + package->next_dialog = NULL; + r = event_do_dialog( package, name ); + HeapFree( GetProcessHeap(), 0, name ); + } + + if( r == ERROR_IO_PENDING ) + r = ERROR_SUCCESS; + + return r; +} + +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 }, + { NULL,NULL }, +}; + +VOID ControlEvent_HandleControlEvent(MSIPACKAGE *package, LPCWSTR event, + LPCWSTR argument, msi_dialog* dialog) +{ + int i = 0; + + TRACE("Handling Control Event %s\n",debugstr_w(event)); + if (!event) + return; + + while( Events[i].event != NULL) + { + LPWSTR wevent = strdupAtoW(Events[i].event); + if (lstrcmpW(wevent,event)==0) + { + HeapFree(GetProcessHeap(),0,wevent); + Events[i].handler(package,argument,dialog); + return; + } + HeapFree(GetProcessHeap(),0,wevent); + i++; + } + FIXME("unhandled control event %s arg(%s)\n", + debugstr_w(event), debugstr_w(argument)); +} diff --git a/dlls/msi/msipriv.h b/dlls/msi/msipriv.h index 778bcee6058..5ba99abf6d2 100644 --- a/dlls/msi/msipriv.h +++ b/dlls/msi/msipriv.h @@ -226,6 +226,7 @@ typedef struct tagMSIPACKAGE LPWSTR next_dialog; BOOL ExecuteSequenceRun; + struct _subscriptions *EventSubscriptions; } MSIPACKAGE; typedef struct tagMSIPREVIEW