msi dialogs, events and office 2003

Aric Stewart aric at codeweavers.com
Thu May 26 12:49:45 CDT 2005


Hi  Stefan,
We have been concentrating alot of effort on Office 2003 and 
specifically on getting office 2003 to install and come up properly. We 
would love to work more directly with you and the whole wine community 
in this goal.

With this patch I have enabled the dialog code that Mike has put into 
wine and started a basic structure for handling ControlEvents sent to 
and from various dialog controls. Alot of this code is still pretty 
hacky and incorrect but the basic functionality and structure is there. 
It also should allow you to actually start installing Office.  With the 
current CVS tip Office2003 fails to install due to actions relating to 
MSXML failing but that is where our effort is now focused.  You can get 
around that by changing the ACTION_ProcessExecSequence(package,FALSE) in 
ACTION_ExecuteAction() to ACTION_ProcessExecSequence(package,TRUE), then 
at least Office 2003 will be able to install its basic functions and 
come up.

We here at CodeWeavers are constantly trying to work closer with the 
wine community.  I am bad about keeping up on wine-devel, so you can 
always e-mail me directly with MSI related things, and i know Mike 
McCormack and a number of other are very good at keeping up on the 
mailing list. I also hang out in the irc channel quite a bit.

With some work and some luck we should be able to get office 2003 
looking pretty good.
-aric
-------------- next part --------------
Index: dlls/msi/Makefile.in
===================================================================
RCS file: /home/wine/wine/dlls/msi/Makefile.in,v
retrieving revision 1.26
diff -u -r1.26 Makefile.in
--- dlls/msi/Makefile.in	9 May 2005 14:42:33 -0000	1.26
+++ dlls/msi/Makefile.in	26 May 2005 16:16:03 -0000
@@ -16,6 +16,7 @@
 	delete.c \
 	dialog.c \
 	distinct.c \
+	events.c \
 	format.c \
 	handle.c \
 	insert.c \
Index: dlls/msi/action.c
===================================================================
RCS file: /home/wine/wine/dlls/msi/action.c,v
retrieving revision 1.123
diff -u -r1.123 action.c
--- dlls/msi/action.c	26 May 2005 16:08:58 -0000	1.123
+++ dlls/msi/action.c	26 May 2005 16:16:06 -0000
@@ -685,6 +685,21 @@
     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 @@
     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 @@
     MSI_RecordSetStringW(row,1,message);
  
     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row);
+
+    ControlEvent_FireSubscribedEvent(package,szActionData, row);
+
     msiobj_release(&row->hdr);
 }
 
@@ -1403,6 +1423,7 @@
     {
         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 ret = FALSE;
 
-    /*
-     * for the UI when we get that working
-     *
     if (ACTION_DialogBox(package,dialog) == ERROR_SUCCESS)
     {
         *rc = package->CurrentInstallState;
         ret = TRUE;
     }
-    */
     return ret;
 }
 
Index: dlls/msi/action.h
===================================================================
RCS file: /home/wine/wine/dlls/msi/action.h,v
retrieving revision 1.9
diff -u -r1.9 action.h
--- dlls/msi/action.h	26 May 2005 16:08:58 -0000	1.9
+++ dlls/msi/action.h	26 May 2005 16:16:06 -0000
@@ -196,3 +196,8 @@
 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);
Index: dlls/msi/msipriv.h
===================================================================
RCS file: /home/wine/wine/dlls/msi/msipriv.h,v
retrieving revision 1.58
diff -u -r1.58 msipriv.h
--- dlls/msi/msipriv.h	26 May 2005 12:24:28 -0000	1.58
+++ dlls/msi/msipriv.h	26 May 2005 16:16:06 -0000
@@ -226,6 +226,7 @@
     LPWSTR next_dialog;
 
     BOOL ExecuteSequenceRun;
+    struct _subscriptions *EventSubscriptions;
 } MSIPACKAGE;
 
 typedef struct tagMSIPREVIEW
--- /dev/null	2005-03-17 08:20:53.000000000 -0600
+++ dlls/msi/events.c	2005-05-26 10:31:14.000000000 -0500
@@ -0,0 +1,506 @@
+/*
+ * 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 <stdarg.h>
+#include <stdio.h>
+
+#define COBJMACROS
+
+#include "windef.h"
+#include "winbase.h"
+#include "winerror.h"
+#include "winreg.h"
+#include "wine/debug.h"
+#include "fdi.h"
+#include "msi.h"
+#include "msiquery.h"
+#include "msvcrt/fcntl.h"
+#include "objbase.h"
+#include "objidl.h"
+#include "msipriv.h"
+#include "winnls.h"
+#include "winuser.h"
+#include "shlobj.h"
+#include "wine/unicode.h"
+#include "ver.h"
+#include "action.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;
+};
+
+static VOID ControlEvent_EndDialog(MSIPACKAGE* package, LPCWSTR argument, 
+                                   msi_dialog* dialog);
+static VOID ControlEvent_NewDialog(MSIPACKAGE* package, LPCWSTR argument, 
+                                   msi_dialog* dialog);
+static VOID ControlEvent_SpawnDialog(MSIPACKAGE* package, LPCWSTR argument, 
+                              msi_dialog* dialog);
+static VOID ControlEvent_SpawnWaitDialog(MSIPACKAGE* package, LPCWSTR argument, 
+                                  msi_dialog* dialog);
+static VOID ControlEvent_DoAction(MSIPACKAGE* package, LPCWSTR argument, 
+                                  msi_dialog* dialog);
+static VOID ControlEvent_AddLocal(MSIPACKAGE* package, LPCWSTR argument, 
+                                  msi_dialog* dialog);
+static VOID ControlEvent_Remove(MSIPACKAGE* package, LPCWSTR argument, 
+                                msi_dialog* dialog);
+static VOID ControlEvent_AddSource(MSIPACKAGE* package, LPCWSTR argument, 
+                                   msi_dialog* dialog);
+
+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 (strcmpW(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));
+}
+
+
+/*
+ * 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 (strcmpW(argument,szExit)==0)
+        package->CurrentInstallState = ERROR_INSTALL_USEREXIT;
+    else if (strcmpW(argument, szRetry) == 0)
+        package->CurrentInstallState = ERROR_INSTALL_SUSPEND;
+    else if (strcmpW(argument, szIgnore) == 0)
+        package->CurrentInstallState = -1;
+    else if (strcmpW(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 (strcmpW(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 (strcmpW(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 (strcmpW(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 (strcmpiW(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 (strcmpiW(package->EventSubscriptions->chain[i].event,event)==0)
+        {
+            struct _subscriber *who;
+            struct _subscriber *prev=NULL;
+            who = package->EventSubscriptions->chain[i].chain;
+            while (who)
+            {
+                if (strcmpiW(who->control,control)==0
+                   && strcmpiW(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 (strcmpiW(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;
+


More information about the wine-devel mailing list