Did a lot of ripping things up.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 22 Apr 2005 13:25:16 +0000
changeset 81 50f6052737ce
parent 80 ab9268f6e92b
child 82 6ade502a3c96
Did a lot of ripping things up. Now all the UI drivers are compiled for all platforms (and nonsense ones, like ui_carbon.c on, say, Windows, will be one-fuction stubs), and the appropriate one is chosen at startup. This lets us pick a GUI one by default, but fall back to stdio if there's an initialization issue or the let the user pick stdio because he's ssh'd to a headless server, etc). It also lets us plug in a Qt and GTK and blahblahblah UI on linux and let everything coexist in one binary. Also, a lot of the bad abstraction breakage in platform_unix.c that got wedged in there when this was a Mac Thing Done In A Hurry has been cleaned up into general application code in mojopatch.c, platform stuff in platform_*, and UI stuff in the UI drivers. Over all, this was a lot of good cleanup that'll make sane Linux and Windows ports much easier.
Makefile
TODO
mojopatch.c
platform.h
platform_unix.c
ui.c
ui.h
ui_carbon.c
ui_stdio.c
--- a/Makefile	Fri Apr 22 10:16:25 2005 +0000
+++ b/Makefile	Fri Apr 22 13:25:16 2005 +0000
@@ -20,19 +20,19 @@
 # you probably shouldn't touch anything below this line.
 
 ifeq ($(strip $(platform)),macosx)
-PLATFORMDEF := -DPLATFORM_UNIX -DPLATFORM_MACOSX
-PLATFORMSRCS := platform_unix.c ui_carbon.c
+PLATFORMDEF := -DPLATFORM_UNIX=1 -DPLATFORM_MACOSX=1
+PLATFORMSRCS := platform_unix.c
 LDFLAGS := -framework Carbon
 endif
 
 ifeq ($(strip $(platform)),win32)
-PLATFORMDEF := -DPLATFORM_WIN32
-PLATFORMSRCS := platform_win32.c ui_stdio.c
+PLATFORMDEF := -DPLATFORM_WIN32=1
+PLATFORMSRCS := platform_win32.c
 endif
 
 ifeq ($(strip $(platform)),unix)
-  PLATFORMDEF := -DPLATFORM_UNIX
-  PLATFORMSRCS := platform_unix.c ui_stdio.c
+  PLATFORMDEF := -DPLATFORM_UNIX=1
+  PLATFORMSRCS := platform_unix.c
 endif
 
 #CFLAGS := $(PLATFORMDEF) -Wall -g -fsigned-char -fno-omit-frame-pointer -O0 -DDEBUG=1 -D_DEBUG=1
@@ -54,7 +54,7 @@
   endif
 endif
 
-MOJOPATCHSRCS := mojopatch.c md5.c $(PLATFORMSRCS)
+MOJOPATCHSRCS := mojopatch.c md5.c ui.c ui_carbon.c ui_stdio.c $(PLATFORMSRCS)
 OBJS1 := $(MOJOPATCHSRCS:.c=.o)
 OBJS2 := $(OBJS1:.cpp=.o)
 OBJS3 := $(OBJS2:.asm=.o)
--- a/TODO	Fri Apr 22 10:16:25 2005 +0000
+++ b/TODO	Fri Apr 22 13:25:16 2005 +0000
@@ -10,7 +10,6 @@
 - Get other platforms besides Mac updated and building again.
 - Look for FIXMEs...
 - "_fatal" isn't really appropriate anymore, since it might not be fatal.
-- chdir_by_identifier() has way too many unwritten responsibilities.
 - We md5sum to compare files. A byte-by-byte compare that halts as soon as
   there's a difference would be much faster when creating the initial patch
   files.
--- a/mojopatch.c	Fri Apr 22 10:16:25 2005 +0000
+++ b/mojopatch.c	Fri Apr 22 13:25:16 2005 +0000
@@ -2022,6 +2022,80 @@
 } /* header_log */
 
 
+static int show_and_install_readme(const char *fname, const char *text)
+{
+    FILE *io = fopen(fname, "wb");
+    if (io == NULL)
+    {
+        _fatal("Failed to open [%s] for writing.", fname);
+        return(0);
+    } /* if */
+
+    /* !!! FIXME: "text" may be binary data, not an asciz string... */
+    fputs(text, io);  /* !!! FIXME: error checking! */
+    fclose(io);
+    return(ui_show_readme(fname, text));
+} /* show_and_install_readme */
+
+
+static int manually_locate_product(const char *name, char *buf, size_t bufsize)
+{
+    const char *promptfmt = "We can't find your \"%s\" installation."
+                            " Would you like to show us where it is?";
+    char *promptstr = alloca(strlen(name) + strlen(promptfmt) + 1);
+
+    if (promptstr == NULL)
+    {
+        _fatal("Out of memory.");
+        return(0);
+    } /* if */
+    sprintf(promptstr, promptfmt, name);
+
+    if (!ui_prompt_yn(promptstr))
+    {
+        _log("User chose not to manually locate installation");
+        return(0);
+    } /* if */
+
+    return(ui_file_picker(buf, bufsize));
+} /* manually_locate_product */
+
+
+static int chdir_by_identifier(const char *name, const char *str,
+                               const char *version, const char *newversion)
+{
+    char buf[MAXPATHLEN];
+    int hasident = ((str != NULL) && (*str));
+    int found = 0;
+
+    if (hasident)
+    {
+        found = locate_product_by_identifier(str, buf, sizeof (buf));
+        if (!found)
+            _log("Couldn't find product. Perhaps it isn't installed?");
+    } /* if */
+
+    if (!found) /* No identifier, or platform layer couldn't find it. */
+    {
+        if (!manually_locate_product(name, buf, sizeof (buf)))
+        {
+            _fatal("We can't patch the product if we can't find it!");
+            return(0);
+        } /* if */
+    } /* if */
+
+    _log("I think the product is installed at [%s].", buf);
+
+    if (chdir(buf) != 0)
+    {
+        _fatal("Failed to change to product's installation directory.");
+        return(0);
+    } /* if */
+
+    return(check_product_version(str, version, newversion));
+} /* chdir_by_identifier */
+
+
 static int process_patch_header(SerialArchive *ar, PatchHeader *h)
 {
     int retval = PATCHSUCCESS;
@@ -2255,6 +2329,8 @@
             make_static_string(header.renamedir, argv[++i]);
         else if (strcmp(argv[i], "--titlebar") == 0)
             make_static_string(header.titlebar, argv[++i]);
+        else if (strcmp(argv[i], "--ui") == 0)
+            i++;  /* (really handled elsewhere.) Just skip ui driver name. */
         else if (strcmp(argv[i], "--zliblevel") == 0)
         {
             zliblevel = atoi(argv[++i]);
@@ -2339,6 +2415,31 @@
 
 /* !!! FIXME: signal_cleanup */
 
+static int kickoff_ui(int argc, char **argv)
+{
+    int seen_ui = 0;
+    int i;
+
+    for (i = 1; i < argc; i++)
+    {
+        if (strcmp(argv[i], "--ui") == 0)
+        {
+            seen_ui = 1;
+            if (ui_init(argv[++i]))
+                return(1);
+        } /* if */
+    } /* for */
+
+    if (!seen_ui)
+    {
+        if (ui_init(NULL))
+            return(1);
+    } /* if */
+
+    fprintf(stderr, "MojoPatch: ui_init() failed!");  /* oh well. */
+    return(0);
+} /* kickoff_ui */
+
 
 int mojopatch_main(int argc, char **argv)
 {
@@ -2347,11 +2448,8 @@
 
     memset(&header, '\0', sizeof (header));
 
-    if (!ui_init())
-    {
-        _fatal("MojoPatch: ui_init() failed!");  /* oh well. */
-        return(PATCHERROR);
-    } /* if */
+    if (!kickoff_ui(argc, argv))
+        return(PATCHERROR);  /* oh well. */
 
     _log("MojoPatch %s starting up.", VERSION);
 
--- a/platform.h	Fri Apr 22 10:16:25 2005 +0000
+++ b/platform.h	Fri Apr 22 13:25:16 2005 +0000
@@ -49,6 +49,7 @@
 /* Call this for logging (debug info). */
 void _dlog(const char *fmt, ...);
 
+/* Does a given version match the requirements? */
 int version_ok(const char *ver, const char *allowed, const char *newver);
 
 /* platform-specific stuff you implement. */
@@ -62,9 +63,8 @@
 int spawn_xdelta(const char *cmdline);
 int update_version(const char *ver);
 int calc_tmp_filenames(char **tmp1, char **tmp2);
-int show_and_install_readme(const char *fname, const char *text);
-int chdir_by_identifier(const char *name, const char *str,
-                        const char *ver, const char *newver);
+int locate_product_by_identifier(const char *str, char *buf, size_t bufsize);
+int check_product_version(const char *ident, const char *version, const char *newversion);
 
 #ifdef __cplusplus
 }
--- a/platform_unix.c	Fri Apr 22 10:16:25 2005 +0000
+++ b/platform_unix.c	Fri Apr 22 13:25:16 2005 +0000
@@ -169,7 +169,7 @@
 } /* get_realpath */
 
 
-#ifdef PLATFORM_MACOSX
+#if PLATFORM_MACOSX
 #include <ApplicationServices/ApplicationServices.h>
 
 static char *parse_xml(char *ptr, char **tag, char **val)
@@ -287,12 +287,13 @@
     
     return(NULL);
 } /* find_info_plist_version */
+#endif  /* PLATFORM_MACOSX */
 
 
-static int parse_info_dot_plist(const char *ident,
-                                const char *version,
-                                const char *newversion)
+int check_product_version(const char *ident, const char *version,
+                          const char *newversion)
 {
+#if PLATFORM_MACOSX
     const char *fname = "Contents/Info.plist";  /* already chdir'd for this. */
     char *mem = NULL;
     char *ptr;
@@ -309,20 +310,24 @@
     io = NULL;
     mem[fsize] = '\0';
 
-    ptr = find_info_plist_bundle_id(mem);
-    if ((ptr == NULL) || (strcasecmp(ptr, ident) != 0))
+    if (ident != NULL)
     {
-        int yes = ui_prompt_ny("We don't think we're looking at the right directory!"
-                               " Are you SURE this is the right place?"
-                               " If you aren't sure, clicking 'Yes' can destroy unrelated files!");
-        if (!yes)
+        ptr = find_info_plist_bundle_id(mem);
+        if ((ptr == NULL) || (strcasecmp(ptr, ident) != 0))
         {
-            _fatal("Stopping at user's request.");
-            free(mem);
-            return(0);
+            int yes = ui_prompt_ny("We don't think we're looking at the right directory!"
+                                   " Are you SURE this is the right place?"
+                                   " If you aren't sure, clicking 'Yes' can destroy unrelated files!");
+            if (!yes)
+            {
+                _fatal("Stopping at user's request.");
+                free(mem);
+                return(0);
+            } /* if */
         } /* if */
     } /* if */
 
+    /* !!! FIXME: this is kinda a lame hack. */
     if ( (io = fopen(fname, "r")) == NULL ) goto parse_info_plist_bailed;
     if ( (fread(mem, fsize, 1, io)) != 1 ) goto parse_info_plist_bailed;
     fclose(io);
@@ -348,11 +353,48 @@
 
     if (!knowver) _fatal("Can't determine product's installed version.");
     return(retval);
+#else
+    _fatal("Not implemented!");  /* !!! FIXME */
+    return(0);
+#endif
 } /* parse_info_dot_plist */
 
 
+int locate_product_by_identifier(const char *str, char *buf, size_t bufsize)
+{
+#if PLATFORM_MACOSX
+    /* Ask LaunchServices to find product by identifier... */
+    OSStatus rc;
+    CFURLRef url = NULL;
+    CFStringRef id = CFStringCreateWithBytes(NULL, str, strlen(str),
+                                             kCFStringEncodingISOLatin1, 0);
+
+    /*
+     * !!! FIXME: This has a tendency to find installs in the trashcan.  :/
+     * !!! FIXME:  It might be worth checking if the product install has
+     * !!! FIXME:  a dir named ".Trash" in the hierarchy, and considering
+     * !!! FIXME:  it not found if so.
+     */
+    rc = LSFindApplicationForInfo(kLSUnknownCreator, id, NULL, NULL, &url);
+    CFRelease(id);
+    if (rc == noErr)
+    {
+        Boolean b = CFURLGetFileSystemRepresentation(url, TRUE, buf, bufsize);
+        CFRelease(url);
+        return(b != 0);
+    } /* if */
+
+    return(0);
+#else
+    _fatal("Not implemented!");  /* !!! FIXME */
+    return(0);
+#endif
+} /* locate_product_by_identifier */
+
+
 int update_version(const char *ver)
 {
+#if PLATFORM_MACOSX
     const char *fname = "Contents/Info.plist";  /* already chdir'd for this. */
     char *mem = NULL;
     char *ptr;
@@ -390,139 +432,18 @@
 
     if (!retval) _fatal("Can't update product's installed version.");
     return(retval);
-} /* update_version */
-
-
-int manually_locate_product(const char *name, char *buf, size_t bufsize);
-
-int chdir_by_identifier(const char *name, const char *str,
-                        const char *version, const char *newversion)
-{
-    char buf[MAXPATHLEN];
-    Boolean b;
-    OSStatus rc;
-    int found = 0;
-    int hasident = ((str != NULL) && (*str));
-
-    /* if an identifier is specified, ask LaunchServices to find product... */
-    if (hasident)
-    {
-        CFURLRef url = NULL;
-        CFStringRef id = CFStringCreateWithBytes(NULL, str, strlen(str),
-                                                kCFStringEncodingISOLatin1, 0);
-
-        rc = LSFindApplicationForInfo(kLSUnknownCreator, id, NULL, NULL, &url);
-        CFRelease(id);
-        if (rc == noErr)
-        {
-            b = CFURLGetFileSystemRepresentation(url, TRUE, buf, sizeof (buf));
-            CFRelease(url);
-            if (!b)
-            {
-                _fatal("Internal error.");
-                return(0);
-            } /* if */
-            found = 1;
-        } /* if */
-        else
-        {
-            _log("Couldn't find product. Perhaps it isn't installed?");
-        } /* if */
-    } /* if */
-
-    if (!found) /* No identifier, or LaunchServices couldn't find it. */
-    {
-        if (!manually_locate_product(name, buf, sizeof (buf)))
-        {
-            _fatal("We can't patch the product if we can't find it!");
-            return(0);
-        } /* if */
-    } /* if */
-
-    _log("I think the product is installed at [%s].", buf);
-
-    if (chdir(buf) != 0)
-    {
-        _fatal("Failed to change to product's installation directory.");
-        return(0);
-    } /* if */
-
-    if (hasident)
-        return(parse_info_dot_plist(str, version, newversion));
-
-    return(1);
-} /* chdir_by_identifier */
-
-
-int show_and_install_readme(const char *fname, const char *text)
-{
-    FILE *io;
-    char *cmd;
-    const char *envr = getenv("HOME");
-    if (!envr)
-    {
-        _fatal("HOME environment var not set?");
-        return(0);
-    } /* if */
-
-    cmd = alloca(strlen(fname) + strlen(envr) + 30);
-    strcpy(cmd, "open ");
-    strcat(cmd, envr);
-    if (cmd[strlen(cmd)-1] != '/')
-        strcat(cmd, "/");
-    strcat(cmd, "Desktop/");
-    strcat(cmd, fname);
-
-    io = fopen(cmd + 5, "w");
-    if (!io)
-    {
-        _fatal("Failed to open [%s] for writing.", cmd+5);
-        return(0);
-    } /* if */
-
-    /* !!! FIXME: error checking! */
-    fputs(text, io);
-    fclose(io);
-    system(cmd);
-    return(1);
-} /* show_and_install_readme */
-
 
 #else  /* Regular old POSIX-compliant Unix... */
 
-int update_version(const char *ver)
-{
     /*
      * !!! FIXME: need some way to flag this install as updated...
      * !!! FIXME:  maybe just leave unimplemented?
      */
     _fatal("Not implemented!");
     return(0);
-} /* show_and_install_readme */
-
-int show_and_install_readme(const char *fname, const char *text)
-{
-    /*
-     * !!! FIXME: Can just dump to stdout? This should really be in the
-     * !!! FIXME:  UI modules...
-     */
-    _fatal("Not implemented!");
-    return(0);
-} /* show_and_install_readme */
-
-
-int chdir_by_identifier(const char *name, const char *str,
-                        const char *version, const char *newversion)
-{
-    /*
-     * !!! FIXME: need some way to find the program automatically...
-     * !!! FIXME:  maybe just prompt the user? Oh well.
-     */
-    _fatal("Not implemented!");
-    return(0);
-} /* chdir_by_identifier */
 
 #endif
+} /* update_version */
 
 
 int calc_tmp_filenames(char **tmp1, char **tmp2)
@@ -569,7 +490,7 @@
         return(0);
     } /* if */
 
-    else if (pid == 0)   // child process.
+    else if (pid == 0)   /* child process. */
     {
         rc = spawn_thread(cmd);
         exit(1);  /* !!! FIXME    *((int *) rc) == 0 ); */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui.c	Fri Apr 22 13:25:16 2005 +0000
@@ -0,0 +1,100 @@
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "platform.h"
+#include "ui.h"
+
+/* stubs for when a driver isn't selected. */
+static void null_ui(void) { assert(0 && "UI call without UI driver!"); }
+static void ui_pump_null(void) { null_ui(); }
+static void ui_add_to_log_null(const char *str, int debugging) { null_ui(); }
+static void ui_fatal_null(const char *str) { null_ui(); }
+static void ui_success_null(const char *str) { null_ui(); }
+static void ui_msgbox_null(const char *str) { null_ui(); }
+static void ui_total_progress_null(int percent) { null_ui(); }
+static void ui_status_null(const char *str) { null_ui(); }
+static void ui_title_null(const char *str) { null_ui(); }
+static int ui_prompt_yn_null(const char *question) { null_ui(); return(0); }
+static int ui_prompt_ny_null(const char *question) { null_ui(); return(0); }
+static void ui_real_deinit_null(void) { null_ui(); }
+static int ui_file_picker_null(char *buf, size_t bufsize) { null_ui(); return(0); }
+static int ui_show_readme_null(const char *fname,const char *text) { null_ui(); return(0); }
+
+
+static int ui_selected = 0;
+
+int ui_init_carbon(void);
+int ui_init_stdio(void);
+
+typedef struct
+{
+    const char *driver_name;
+    int (*initfunc)(void);
+} UIDrivers;
+
+static UIDrivers ui_drivers[] =
+{
+    { "carbon", ui_init_carbon },
+    { "stdio", ui_init_stdio },  /* should probably always be last. */
+    { NULL, NULL }
+};
+
+int ui_init(const char *request_driver)
+{
+    int i;
+
+    if (ui_selected)
+        return(1);
+
+    /* make sure we're in a sane state... */
+    UI_SET_FUNC_POINTERS(null);
+
+    if (request_driver != NULL)
+    {
+        for (i = 0; ui_drivers[i].initfunc != NULL; i++)
+        {
+            if (strcmp(ui_drivers[i].driver_name, request_driver) == 0)
+            {
+                ui_selected = ui_drivers[i].initfunc();
+                break;
+            } /* if */
+        } /* for */
+    } /* if */
+
+    for (i = 0; (!ui_selected) && (ui_drivers[i].initfunc != NULL); i++)
+        ui_selected = ui_drivers[i].initfunc();
+
+    return(ui_selected);
+} /* ui_init */
+
+
+void ui_deinit(void)
+{
+    if (!ui_selected)
+        return;
+
+    ui_real_deinit();
+    UI_SET_FUNC_POINTERS(null);
+    ui_selected = 0;
+} /* ui_deinit */
+
+
+/* The driver function pointers... */
+void (*ui_real_deinit)(void) = ui_real_deinit_null;
+void (*ui_pump)(void) = ui_pump_null;
+void (*ui_add_to_log)(const char *str, int debugging) = ui_add_to_log_null;
+void (*ui_fatal)(const char *str) = ui_fatal_null;
+void (*ui_success)(const char *str) = ui_success_null;
+void (*ui_msgbox)(const char *str) = ui_msgbox_null;
+void (*ui_total_progress)(int percent) = ui_total_progress_null;
+void (*ui_status)(const char *str) = ui_status_null;
+void (*ui_title)(const char *str) = ui_title_null;
+int (*ui_prompt_yn)(const char *question) = ui_prompt_yn_null;
+int (*ui_prompt_ny)(const char *question) = ui_prompt_ny_null;
+int (*ui_file_picker)(char *buf, size_t bufsize) = ui_file_picker_null;
+int (*ui_show_readme)(const char *fname,const char *text)=ui_show_readme_null;
+
+/* end of ui.c ... */
+
--- a/ui.h	Fri Apr 22 10:16:25 2005 +0000
+++ b/ui.h	Fri Apr 22 13:25:16 2005 +0000
@@ -6,19 +6,48 @@
 extern "C" {
 #endif
 
-/* user interface stuff you implement. */
-int ui_init(void);
+/* user interface stuff you implement: see ui.c and ui_stdio.c for examples */
+int ui_init(const char *request_driver);
 void ui_deinit(void);
-void ui_pump(void);
-void ui_add_to_log(const char *str, int debugging);
-void ui_fatal(const char *str);
-void ui_success(const char *str);
-void ui_msgbox(const char *str);
-void ui_total_progress(int percent);
-void ui_status(const char *str);
-void ui_title(const char *str);
-int ui_prompt_yn(const char *question);
-int ui_prompt_ny(const char *question);
+extern void (*ui_pump)(void);
+extern void (*ui_add_to_log)(const char *str, int debugging);
+extern void (*ui_fatal)(const char *str);
+extern void (*ui_success)(const char *str);
+extern void (*ui_msgbox)(const char *str);
+extern void (*ui_total_progress)(int percent);
+extern void (*ui_status)(const char *str);
+extern void (*ui_title)(const char *str);
+extern int (*ui_prompt_yn)(const char *question);
+extern int (*ui_prompt_ny)(const char *question);
+extern int (*ui_file_picker)(char *buf, size_t bufsize);
+extern int (*ui_show_readme)(const char *fname, const char *text);
+
+/* this is for the UI layer, the application uses ui_deinit() instead... */
+extern void (*ui_real_deinit)(void);
+
+/*
+ * Macros for use by the UI layer. The application should ignore this.
+ *
+ * (This all feels a little naughty, but it guarantees we'll catch it when a
+ *  a new entry point is added and a driver hasn't been updated.)
+ */
+#define UI_SET_FUNC_POINTER(func,drv) { func = func##_##drv; }
+#define UI_SET_FUNC_POINTERS(drv) \
+{ \
+    UI_SET_FUNC_POINTER(ui_real_deinit,drv) \
+    UI_SET_FUNC_POINTER(ui_pump,drv) \
+    UI_SET_FUNC_POINTER(ui_add_to_log,drv) \
+    UI_SET_FUNC_POINTER(ui_fatal,drv) \
+    UI_SET_FUNC_POINTER(ui_success,drv) \
+    UI_SET_FUNC_POINTER(ui_msgbox,drv) \
+    UI_SET_FUNC_POINTER(ui_total_progress,drv) \
+    UI_SET_FUNC_POINTER(ui_status,drv) \
+    UI_SET_FUNC_POINTER(ui_title,drv) \
+    UI_SET_FUNC_POINTER(ui_prompt_yn,drv) \
+    UI_SET_FUNC_POINTER(ui_prompt_ny,drv) \
+    UI_SET_FUNC_POINTER(ui_file_picker,drv) \
+    UI_SET_FUNC_POINTER(ui_show_readme,drv) \
+}
 
 #ifdef __cplusplus
 }
--- a/ui_carbon.c	Fri Apr 22 10:16:25 2005 +0000
+++ b/ui_carbon.c	Fri Apr 22 13:25:16 2005 +0000
@@ -1,3 +1,8 @@
+
+#if !PLATFORM_MACOSX
+int ui_init_carbon(void) { return(0); } /* not implemented if not MacOS. */
+#else
+
 #include <Carbon/Carbon.h>
 
 #include "platform.h"
@@ -10,95 +15,47 @@
 static WindowRef window;
 static ControlRef progress;
 static ControlRef status;
-static int carbon_ui_initialized = 0;
 
-/* user interface stuff you implement. */
-int ui_init(void)
-{
-    ControlID statusID = { MOJOPATCH_SIG, MOJOPATCH_STATUS_ID };
-    ControlID progressID = { MOJOPATCH_SIG, MOJOPATCH_PROGRESS_ID };
-    IBNibRef nibRef = NULL;
-    OSStatus err;
-    Boolean b = TRUE;
-
-    if (carbon_ui_initialized)  /* already initialized? */
-        return(1);
-
-    /* !!! FIXME: This is corrupting the "basedir" variable in platform_unix.c ! */
-    if (CreateNibReference(CFSTR("mojopatch"), &nibRef) != noErr)
-    {
-        fprintf(stderr, "MOJOPATCH: You probably don't have a .nib file!\n");
-        return(0);  /* usually .nib isn't found. */
-    } /* if */
-
-    err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
-    if (err == noErr)
-        err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
-    DisposeNibReference( nibRef );
-
-    if (err == noErr)
-        err = GetControlByID(window, &statusID, &status);
-
-    if (err == noErr)
-        err = GetControlByID(window, &progressID, &progress);
-
-    if (err == noErr)
-    {
-        ShowWindow(window);
-        err = ActivateWindow(window, TRUE);
-    } /* if */
-
-    if (err == noErr)
-    {
-        err = SetControlData(progress, kControlEntireControl,
-                              kControlProgressBarAnimatingTag,
-                              sizeof (b), &b);
-    } /* if */
-
-    carbon_ui_initialized = 1;
-    return(err == noErr);
-} /* ui_init */
-
-
-void ui_title(const char *str)
-{
-    CFStringRef cfstr = CFStringCreateWithBytes(NULL, str, strlen(str),
-                                                kCFStringEncodingISOLatin1, 0);
-    SetWindowTitleWithCFString(window, cfstr);
-    CFRelease(cfstr);
-    ui_pump();
-} /* ui_title */
-
-
-void ui_deinit(void)
-{
-    /* !!! FIXME */
-    /* carbon_ui_initialized = 0; */
-} /* ui_deinit */
-
-
-void ui_pump(void)
+static void ui_pump_carbon(void)
 {
     EventRef theEvent;
     EventTargetRef theTarget;
 
-    if (!carbon_ui_initialized)
-        return;
-
     theTarget = GetEventDispatcherTarget();
     if (ReceiveNextEvent(0, NULL, 0, true, &theEvent) == noErr)
     {
         SendEventToEventTarget(theEvent, theTarget);
         ReleaseEvent(theEvent);
     } /* if */
-} /* ui_pump */
+} /* ui_pump_carbon */
+
+
+static void ui_title_carbon(const char *str)
+{
+    CFStringRef cfstr = CFStringCreateWithBytes(NULL, str, strlen(str),
+                                                kCFStringEncodingISOLatin1, 0);
+    SetWindowTitleWithCFString(window, cfstr);
+    CFRelease(cfstr);
+    ui_pump_carbon();
+} /* ui_title_carbon */
 
 
-void ui_add_to_log(const char *str, int debugging)
+static void ui_real_deinit_carbon(void)
 {
     /* !!! FIXME */
+} /* ui_real_deinit_carbon */
+
+
+static void ui_add_to_log_carbon(const char *str, int debugging)
+{
+    /*
+     * stdout in a Mac GUI app shows up in the system console, which can be
+     *  viewed via /Applications/Utilities/Console.app ...
+     */
+
+    /* !!! FIXME */
     printf("MojoPatch%s: %s\n", debugging ? " [debug]" : "", str);
-} /* ui_add_to_log */
+} /* ui_add_to_log_carbon */
 
 
 static int do_msgbox(const char *str, AlertType alert_type,
@@ -155,18 +112,20 @@
     return(item == kAlertStdAlertOKButton);
 } /* ui_prompt_yes_or_no */
 
-int ui_prompt_yn(const char *question)
+
+static int ui_prompt_yn_carbon(const char *question)
 {
     return(ui_prompt_yes_or_no(question, 1));
-} /* ui_prompt_yn */
+} /* ui_prompt_yn_carbon */
 
-int ui_prompt_ny(const char *question)
+
+static int ui_prompt_ny_carbon(const char *question)
 {
     return(ui_prompt_yes_or_no(question, 1));  /* !!! FIXME! should be zero. */
-} /* ui_prompt_ny */
+} /* ui_prompt_ny_carbon */
 
 
-int manually_locate_product(const char *name, char *buf, size_t bufsize)
+static int ui_file_picker_carbon(char *buf, size_t bufsize)
 {
     NavDialogCreationOptions dlgopt;
     NavDialogRef dlg;
@@ -177,30 +136,12 @@
     FSRef fsref;
     OSStatus rc;
     int retval = 0;
-    int yn;
-    const char *promptfmt = "We can't find your \"%s\" installation."
-                            " Would you like to show us where it is?";
-    char *promptstr = alloca(strlen(name) + strlen(promptfmt) + 1);
-
-    if (promptstr == NULL)
-    {
-        _fatal("Out of memory.");
-        return(0);
-    } /* if */
-    sprintf(promptstr, promptfmt, name);
-
-    yn = ui_prompt_yn(promptstr);
-    if (!yn)
-    {
-        _log("User chose not to manually locate installation");
-        return(0);
-    } /* if */
 
     NavGetDefaultDialogCreationOptions(&dlgopt);
     dlgopt.optionFlags |= kNavSupportPackages;
     dlgopt.optionFlags |= kNavAllowOpenPackages;
     dlgopt.optionFlags &= ~kNavAllowMultipleFiles;
-    dlgopt.windowTitle = CFSTR("Please select the product's icon and click 'OK'.");
+    dlgopt.windowTitle = CFSTR("Please select the product's icon and click 'OK'.");  /* !!! FIXME! */
     dlgopt.actionButtonLabel = CFSTR("OK");
     NavCreateChooseFolderDialog(&dlgopt, NULL, NULL, NULL, &dlg);
     NavDialogRun(dlg);
@@ -212,7 +153,7 @@
         NavDialogGetReply(dlg, &reply);
         rc = AEGetNthDesc(&reply.selection, 1, typeFSRef, &keyword, &desc);
         if (rc != noErr)
-            _fatal("Unexpected error in AEGetNthDesc: %d\n", (int) rc);
+            _fatal("Unexpected error in AEGetNthDesc: %d", (int) rc);
         else
         {
             /* !!! FIXME: Check return values here! */
@@ -232,31 +173,28 @@
             retval ? "selected" : "did NOT select");
 
     return(retval);
-} /* manually_locate_product */
+} /* ui_file_picker_carbon */
 
 
-void ui_fatal(const char *str)
+static void ui_fatal_carbon(const char *str)
 {
-    if (!carbon_ui_initialized)
-        fprintf(stderr, "FATAL ERROR: %s\n", str);
-    else
-        do_msgbox(str, kAlertStopAlert, NULL, NULL);
+    do_msgbox(str, kAlertStopAlert, NULL, NULL);
 } /* ui_fatal */
 
 
-void ui_success(const char *str)
+static void ui_success_carbon(const char *str)
 {
     do_msgbox(str, kAlertNoteAlert, NULL, NULL);
-} /* ui_success */
+} /* ui_success_carbon */
 
 
-void ui_msgbox(const char *str)
+static void ui_msgbox_carbon(const char *str)
 {
     do_msgbox(str, kAlertNoteAlert, NULL, NULL);
-} /* ui_msgbox */
+} /* ui_msgbox_carbon */
 
 
-void ui_total_progress(int percent)
+static void ui_total_progress_carbon(int percent)
 {
     static int lastpercent = -1;
     if (percent != lastpercent)
@@ -268,15 +206,87 @@
         SetControl32BitValue(progress, percent);
         lastpercent = percent;
     } /* if */
-} /* ui_total_progress */
+} /* ui_total_progress_carbon */
 
 
-void ui_status(const char *str)
+static void ui_status_carbon(const char *str)
 {
     SetControlData(status, kControlEditTextPart, kControlStaticTextTextTag,
                     strlen(str), str);
     Draw1Control(status);
-} /* ui_status */
+} /* ui_status_carbon */
+
+
+static int ui_show_readme_carbon(const char *fname, const char *text)
+{
+    /*
+     * Just let the Finder pick the right program to view the file...
+     *  this lets you ship with a .txt, .html, .rtf, or whatever, for a
+     *  readme on this platform.
+     */
+
+    size_t allocsize = strlen(fname) + 32;
+    char *cmd = (char *) alloca(allocsize);
+    if (!cmd)
+    {
+        _fatal("Out of memory.");
+        return(0);
+    } /* if */
+
+    snprintf(cmd, allocsize, "open %s", fname);
+    system(cmd);  /* !!! FIXME: error check? */
+    return(1);
+} /* ui_show_readme_carbon */
+
+
+/* user interface stuff you implement. */
+int ui_init_carbon(void)
+{
+    ControlID statusID = { MOJOPATCH_SIG, MOJOPATCH_STATUS_ID };
+    ControlID progressID = { MOJOPATCH_SIG, MOJOPATCH_PROGRESS_ID };
+    IBNibRef nibRef = NULL;
+    OSStatus err;
+    Boolean b = TRUE;
+
+    /* !!! FIXME: This is corrupting the "basedir" variable in platform_unix.c ! */
+    if (CreateNibReference(CFSTR("mojopatch"), &nibRef) != noErr)
+    {
+        fprintf(stderr, "MOJOPATCH: Carbon UI failed to initialize!\n");
+        fprintf(stderr, "MOJOPATCH: You probably don't have a .nib file!\n");
+        return(0);  /* usually .nib isn't found. */
+    } /* if */
+
+    err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
+    if (err == noErr)
+        err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
+    DisposeNibReference( nibRef );
+
+    if (err == noErr)
+        err = GetControlByID(window, &statusID, &status);
+
+    if (err == noErr)
+        err = GetControlByID(window, &progressID, &progress);
+
+    if (err == noErr)
+    {
+        ShowWindow(window);
+        err = ActivateWindow(window, TRUE);
+    } /* if */
+
+    if (err == noErr)
+    {
+        err = SetControlData(progress, kControlEntireControl,
+                              kControlProgressBarAnimatingTag,
+                              sizeof (b), &b);
+    } /* if */
+
+    if (err == noErr);
+        UI_SET_FUNC_POINTERS(carbon);
+
+    return(err == noErr);
+} /* ui_init_carbon */
+
+#endif
 
 /* end of ui_carbon.c ... */
 
--- a/ui_stdio.c	Fri Apr 22 10:16:25 2005 +0000
+++ b/ui_stdio.c	Fri Apr 22 13:25:16 2005 +0000
@@ -1,53 +1,50 @@
 
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <ctype.h>
 
 #include "platform.h"
 #include "ui.h"
 
-int ui_init(void)
+static void ui_title_stdio(const char *str)
 {
-    return(1); /* always succeeds. */
-} /* ui_init */
+    printf("=== %s ===\n\n", str);
+} /* ui_title_stdio */
 
 
-void ui_title(const char *str)
-{
-} /* ui_title */
-
-
-void ui_deinit(void)
+static void ui_real_deinit_stdio(void)
 {
     printf("\n\nHit enter to quit.\n\n");
     getchar();
-} /* ui_deinit */
+} /* ui_deinit_stdio */
 
 
-void ui_pump(void)
+static void ui_pump_stdio(void)
 {
     /* no-op. */
-} /* ui_pump */
+} /* ui_pump_stdio */
 
 
-void ui_add_to_log(const char *str, int debugging)
+static void ui_add_to_log_stdio(const char *str, int debugging)
 {
     printf("%s%s\n", debugging ? "debug: " : "", str);
-} /* ui_add_to_log */
+} /* ui_add_to_log_stdio */
 
 
-void ui_fatal(const char *str)
+static void ui_fatal_stdio(const char *str)
 {
     fprintf(stderr, "\n%s\n\n", str);
-} /* ui_fatal */
+} /* ui_fatal_stdio */
 
 
-void ui_success(const char *str)
+static void ui_success_stdio(const char *str)
 {
     fprintf(stderr, "\n%s\n\n", str);
-} /* ui_success */
+} /* ui_success_stdio */
 
 
-void ui_total_progress(int percent)
+static void ui_total_progress_stdio(int percent)
 {
     static int lastpercent = -1;
     if (percent != lastpercent)
@@ -57,16 +54,16 @@
         if (percent == 100)
             printf("\n");
     } /* if */
-} /* ui_total_progress */
+} /* ui_total_progress_stdio */
 
 
-void ui_status(const char *str)
+static void ui_status_stdio(const char *str)
 {
     printf("Current operation: %s\n", str);
-} /* ui_status */
+} /* ui_status_stdio */
 
 
-int ui_prompt_yn(const char *question)
+static int ui_prompt_yn_stdio(const char *question)
 {
     int c;
     while (1)
@@ -81,10 +78,10 @@
     } /* while */
 
     return(1);
-} /* ui_prompt_yn */
+} /* ui_prompt_yn_stdio */
 
 
-int ui_prompt_ny(const char *question)
+static int ui_prompt_ny_stdio(const char *question)
 {
     int c;
     while (1)
@@ -99,7 +96,68 @@
     } /* while */
 
     return(0);
-} /* ui_prompt_ny */
+} /* ui_prompt_ny_stdio */
+
+
+static void ui_msgbox_stdio(const char *str)
+{
+    printf("\n\n-----\n%s\n-----\n\nHit enter to continue.\n\n", str);
+    getchar();
+} /* ui_msgbox_stdio */
+
+
+static int ui_file_picker_stdio(char *buf, size_t bufsize)
+{
+    while (1)
+    {
+        size_t len;
+        puts("Please enter the path, or blank to cancel.\n> ");
+
+        buf[0] = '\0';
+        fgets(buf, bufsize, stdin);
+        len = strlen(buf);
+        while ((len > 0) && ((buf[len-1] == '\n') || (buf[len-1] == '\r')))
+            buf[--len] = '\0';
+
+        if (len == 0)
+            return(0);  /* user "cancelled". */
+
+        if (file_exists(buf))
+            return(1);  /* user entered valid path. */
+
+        puts("That path does not exist. Please try again.\n\n");
+    } /* while */
 
-/* end of ui_stdio.h ... */
+    return(0);  /* should never hit this. */
+} /* ui_file_picker_stdio */
+
+
+static int ui_show_readme_stdio(const char *fname,const char *text)
+{
+    /*
+     * Cheat for now and push this off to "less", which will warn if the
+     *  readme is a binary file and prompt the user about avoiding it.
+     */
 
+    size_t allocsize = strlen(fname) + 32;
+    char *cmd = (char *) alloca(allocsize);
+    if (!cmd)
+    {
+        _fatal("Out of memory.");
+        return(0);
+    } /* if */
+
+    snprintf(cmd, allocsize, "less %s", fname);
+    system(cmd);  /* !!! FIXME: error check? */
+    return(1);
+} /* ui_show_readme_stdio */
+
+
+int ui_init_stdio(void)
+{
+    UI_SET_FUNC_POINTERS(stdio);
+    return(1); /* always succeeds. */
+} /* ui_init */
+
+/* end of ui_stdio.c ... */
+