From d97836f5dd8f94e5e55c65ab21a9ddfa82b919cd Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Mon, 23 Dec 2019 14:33:24 +0100 Subject: [PATCH] macOS: fixed all demo programs that need to access resources MacOS uses bundles instead of executables. CMake creates those bundles in various locations, depending on the generator used (Xcode or Makefiles). I tried to fix all instances where demo apps did not find the resources they needed. This probably must be done for Linux and MSWindows as well. --- test/CMakeLists.txt | 20 +++++-- test/demo.cxx | 133 ++++++++++++++++++++++++++++---------------- test/demo.menu | 2 +- 3 files changed, 101 insertions(+), 54 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4d5c0f031..b3256d5b2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -156,9 +156,21 @@ configure_file(rgb.txt ${TESTFILE_PATH} COPYONLY) configure_file(help_dialog.html ${TESTFILE_PATH} COPYONLY) configure_file(browser.cxx ${TESTFILE_PATH} COPYONLY) configure_file(editor.cxx ${TESTFILE_PATH} COPYONLY) + +# Apple macOS creates bundles instead of executables and needs a little bit +# more help for demos to run correctly if(APPLE AND NOT OPTION_APPLE_X11) - configure_file(demo.menu "${TESTFILE_PATH}/demo.app/Contents/Resources/demo.menu" COPYONLY) - configure_file(browser.cxx "${TESTFILE_PATH}/browser.app/Contents/Resources/browser.cxx" COPYONLY) - configure_file(rgb.txt ${TESTFILE_PATH}/colbrowser.app/Contents/Resources/rgb.txt COPYONLY) - configure_file(help_dialog.html ${TESTFILE_PATH}/help_dialog.app/Contents/Resources/help_dialog.html COPYONLY) + + # make the menu structure part of the app + target_sources(demo PRIVATE demo.menu) + set_target_properties(demo PROPERTIES MACOSX_BUNDLE TRUE RESOURCE demo.menu ) + + # add a sample RGB file that otherwise only exists under X11 + target_sources(colbrowser PRIVATE rgb.txt) + set_target_properties(colbrowser PROPERTIES MACOSX_BUNDLE TRUE RESOURCE rgb.txt ) + + # help_dialog displays an html file as an example + target_sources(help_dialog PRIVATE help_dialog.html) + set_target_properties(help_dialog PROPERTIES MACOSX_BUNDLE TRUE RESOURCE help_dialog.html ) + endif(APPLE AND NOT OPTION_APPLE_X11) diff --git a/test/demo.cxx b/test/demo.cxx index aada148a6..0b72715a1 100644 --- a/test/demo.cxx +++ b/test/demo.cxx @@ -3,7 +3,7 @@ // // Main demo program for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2018 by Bill Spitzak and others. +// Copyright 1998-2019 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -39,7 +39,6 @@ #if defined __APPLE__ #include -#include // no longer necessary with fl_chdir() ? #endif #include @@ -293,58 +292,94 @@ void dobut(Fl_Widget *, long arg) { delete[] copy_of_icommand; #elif defined __APPLE__ + /* + Starting with version 1.4.0, FLTK uses CMake as the only supported build + system. On macOS, the app developer is expected to run CMake in a + directory named './build/Xcode' or './build/Makefiles' to generate the + build environment. - char *cmd = strdup(menus[men].icommand[bn]); - char *macosArg = strchr(cmd, ' '); - - char command[2048], path[2048], app_path[2048]; - - // this neat little block of code ensures that the current directory - // is set to the location of the Demo application. - CFBundleRef app = CFBundleGetMainBundle(); - CFURLRef url = CFBundleCopyBundleURL(app); - CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); - CFRelease(url); - CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8); - CFRelease(cc_app_path); - if (*app_path) { - if (memcmp(app_path + strlen(app_path) - 4, ".app", 4) == 0) { - char *lastSlash = strrchr(app_path, '/'); - if (lastSlash) *lastSlash = 0; + When building FLTK in the next step, teh macOS app bundles are then + stored in either: + './build/Xcode/bin/examples/hello.app/' for Makefiles + './build/Xcode/bin/examples/Debug/hello.app/' for XCode Debug + or + './build/Xcode/bin/examples/Release/hello.app/' as a symbolic + into the Archive system of macOS + + 'Demo' needs to find and run all of these app bundles, some requiring + an additional file name and path for resource files. + + This is my attempt to find the bundles and resources so that Demo.app + and all its dependencies will run without any further configuration. + They will stop running however if any of the bundles or rresources + are moved. + */ + { + char src_path[PATH_MAX]; + char app_path[PATH_MAX]; + char app_name[PATH_MAX]; + char command[2*PATH_MAX+2]; + char *cmd = strdup(menus[men].icommand[bn]); + char *args = strchr(cmd, ' '); + + /* + Get the path to the source code at compile time. This is where the other + resources are located. + */ + strcpy(src_path, __FILE__); + char *src_path_end = (char*)fl_filename_name(src_path); + if (src_path_end) *src_path_end = 0; + + /* + All example app bundles are in the same directory as 'Demo', so set the + current dir to the location of Demo.app . + + Starting with macOS 10.12, the actual location of the app has a randomized + path to fix a vulnerability. This still works in Debug mode which is + */ + { + app_path[0] = 0; + CFBundleRef app = CFBundleGetMainBundle(); + CFURLRef url = CFBundleCopyBundleURL(app); + CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); + CFRelease(url); + CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8); + CFRelease(cc_app_path); + if (app_path[0]) { + char *app_path_end = (char*)fl_filename_name(app_path); + if (app_path_end) *app_path_end = 0; + fl_chdir(app_path); + } } - fl_chdir(app_path); - } - - char *name = new char[strlen(cmd) + 5]; - strcpy(name, cmd); - if (macosArg) name[macosArg-cmd] = 0; - strcat(name, ".app"); - // check whether app bundle exists - if ( ! fl_filename_isdir(name) ) strcpy(name, cmd); - if (macosArg) { - const char *fluidpath; - *macosArg = 0; -#if defined USING_XCODE - fl_filename_absolute(path, 2048, "../../../../test/"); - fluidpath = "fluid.app"; -#else - strcpy(path, app_path); strcat(path, "/"); - fluidpath = "../fluid/fluid.app"; - // check whether fluid bundle exists - if ( ! fl_filename_isdir(fluidpath) ) fluidpath = "../fluid"; -#endif - if (strcmp(cmd, "../fluid/fluid")==0) { - sprintf(command, "open %s --args %s%s", fluidpath, path, macosArg+1); + + // extract the executable name from the command in the menu file + strcpy(app_name, cmd); + // remove any additioanl command line arguments + if (args) app_name[args-cmd] = 0; + // make the file name into a bundle name + strcat(app_name, ".app"); + + if (args) { + if (strcmp(app_name, "../fluid/fluid.app")==0) { + // CMake -G 'Unix Makefiles' ... : ./bin/fluid.app + // CMake -G 'Xcode' ... : ./bin/Debug/fluid.app or ./bin/Release/fluid.app + // so removing the '/example' path segment from the app_path should + // always work. + char *examples = strstr(app_path, "/examples"); + if (examples) { + memmove(examples, examples+9, strlen(examples+9)+1); + } + sprintf(command, "open '%sfluid.app' --args '%s%s'", app_path, src_path, args+1); + } else { + // we assume that we have only one argument which is a filename, so we add a path + sprintf(command, "open '%s%s' --args '%s%s'", app_path, app_name, src_path, args+1); + } } else { - sprintf(command, "open %s --args %s%s", name, path, macosArg+1); + sprintf(command, "open '%s%s'", app_path, app_name); } - } else { - sprintf(command, "open %s", name); + system(command); + free(cmd); } - delete[] name; - // puts(command); - system(command); - free(cmd); #else // Non Windows systems. diff --git a/test/demo.menu b/test/demo.menu index 97e522a58..177fb1803 100644 --- a/test/demo.menu +++ b/test/demo.menu @@ -77,7 +77,7 @@ @o:Font Tests...:@of @of:Fonts:fonts @of:UTF-8:utf8 - @o:HelpDialog:help_dialog + @o:HelpDialog:help_dialog help_dialog.html @o:Input Choice:input_choice @o:Preferences:preferences @o:Threading:threads