| @@ -427,6 +427,22 @@ attributes directly to these creation functions, rather than adding them later. | |||
| plist if `APP_SANDBOX_ENABLED` is `TRUE`. Each key should be in the form `com.apple.security.*` | |||
| where `*` is a specific entitlement. | |||
| `APP_SANDBOX_FILE_ACCESS_HOME_RO` | |||
| - A set of space-separated paths that will be added to this target's entitlements plist for | |||
| accessing read-only paths relative to the home directory if `APP_SANDBOX_ENABLED` is `TRUE`. | |||
| `APP_SANDBOX_FILE_ACCESS_HOME_RW` | |||
| - A set of space-separated paths that will be added to this target's entitlements plist for | |||
| accessing read/write paths relative to the home directory if `APP_SANDBOX_ENABLED` is `TRUE`. | |||
| `APP_SANDBOX_FILE_ACCESS_ABS_RO` | |||
| - A set of space-separated paths that will be added to this target's entitlements plist for | |||
| accessing read-only absolute paths if `APP_SANDBOX_ENABLED` is `TRUE`. | |||
| `APP_SANDBOX_FILE_ACCESS_ABS_RW` | |||
| - A set of space-separated paths that will be added to this target's entitlements plist for | |||
| accessing read/write absolute paths if `APP_SANDBOX_ENABLED` is `TRUE`. | |||
| `PLIST_TO_MERGE` | |||
| - A string to insert into an app/plugin's Info.plist. | |||
| @@ -318,6 +318,10 @@ function(_juce_write_configure_time_info target) | |||
| _juce_append_target_property(file_content APP_SANDBOX_INHERIT ${target} JUCE_APP_SANDBOX_INHERIT) | |||
| _juce_append_target_property(file_content HARDENED_RUNTIME_OPTIONS ${target} JUCE_HARDENED_RUNTIME_OPTIONS) | |||
| _juce_append_target_property(file_content APP_SANDBOX_OPTIONS ${target} JUCE_APP_SANDBOX_OPTIONS) | |||
| _juce_append_target_property(file_content APP_SANDBOX_FILE_ACCESS_HOME_RO ${target} JUCE_APP_SANDBOX_FILE_ACCESS_HOME_RO) | |||
| _juce_append_target_property(file_content APP_SANDBOX_FILE_ACCESS_HOME_RW ${target} JUCE_APP_SANDBOX_FILE_ACCESS_HOME_RW) | |||
| _juce_append_target_property(file_content APP_SANDBOX_FILE_ACCESS_ABS_RO ${target} JUCE_APP_SANDBOX_FILE_ACCESS_ABS_RO) | |||
| _juce_append_target_property(file_content APP_SANDBOX_FILE_ACCESS_ABS_RW ${target} JUCE_APP_SANDBOX_FILE_ACCESS_ABS_RW) | |||
| _juce_append_target_property(file_content APP_GROUPS_ENABLED ${target} JUCE_APP_GROUPS_ENABLED) | |||
| _juce_append_target_property(file_content APP_GROUP_IDS ${target} JUCE_APP_GROUP_IDS) | |||
| _juce_append_target_property(file_content IS_PLUGIN ${target} JUCE_IS_PLUGIN) | |||
| @@ -1546,6 +1550,10 @@ function(_juce_initialise_target target) | |||
| VST3_CATEGORIES | |||
| HARDENED_RUNTIME_OPTIONS | |||
| APP_SANDBOX_OPTIONS | |||
| APP_SANDBOX_FILE_ACCESS_HOME_RO | |||
| APP_SANDBOX_FILE_ACCESS_HOME_RW | |||
| APP_SANDBOX_FILE_ACCESS_ABS_RO | |||
| APP_SANDBOX_FILE_ACCESS_ABS_RW | |||
| DOCUMENT_EXTENSIONS | |||
| AAX_CATEGORY | |||
| IPHONE_SCREEN_ORIENTATIONS # iOS only | |||
| @@ -79,7 +79,7 @@ namespace build_tools | |||
| if (isAppGroupsEnabled) | |||
| { | |||
| auto appGroups = StringArray::fromTokens (appGroupIdString, ";", {}); | |||
| auto groups = String ("<array>"); | |||
| String groups = "<array>"; | |||
| for (auto group : appGroups) | |||
| groups += "\n\t\t<string>" + group.trim() + "</string>"; | |||
| @@ -101,13 +101,27 @@ namespace build_tools | |||
| { | |||
| // no other sandbox options can be specified if sandbox inheritance is enabled! | |||
| jassert (appSandboxOptions.isEmpty()); | |||
| jassert (appSandboxTemporaryPaths.empty()); | |||
| entitlements.set ("com.apple.security.inherit", "<true/>"); | |||
| } | |||
| if (isAppSandboxEnabled) | |||
| { | |||
| for (auto& option : appSandboxOptions) | |||
| entitlements.set (option, "<true/>"); | |||
| for (auto& option : appSandboxTemporaryPaths) | |||
| { | |||
| String paths = "<array>"; | |||
| for (const auto& path : option.values) | |||
| paths += "\n\t\t<string>" + path + "</string>"; | |||
| paths += "\n\t</array>"; | |||
| entitlements.set (option.key, paths); | |||
| } | |||
| } | |||
| } | |||
| if (isNetworkingMulticastEnabled) | |||
| @@ -49,6 +49,14 @@ namespace build_tools | |||
| StringArray hardenedRuntimeOptions; | |||
| StringArray appSandboxOptions; | |||
| struct KeyAndStringArray | |||
| { | |||
| String key; | |||
| StringArray values; | |||
| }; | |||
| std::vector<KeyAndStringArray> appSandboxTemporaryPaths; | |||
| private: | |||
| StringPairArray getEntitlements() const; | |||
| }; | |||
| @@ -343,6 +343,29 @@ juce::build_tools::EntitlementOptions parseEntitlementsOptions (const juce::File | |||
| updateField ("APP_SANDBOX_OPTIONS", result.appSandboxOptions); | |||
| updateField ("NETWORK_MULTICAST_ENABLED", result.isNetworkingMulticastEnabled); | |||
| struct SandboxTemporaryAccessKey | |||
| { | |||
| juce::String cMakeVar, key; | |||
| }; | |||
| SandboxTemporaryAccessKey sandboxTemporaryAccessKeys[] | |||
| { | |||
| { "APP_SANDBOX_FILE_ACCESS_HOME_RO", "home-relative-path.read-only" }, | |||
| { "APP_SANDBOX_FILE_ACCESS_HOME_RW", "home-relative-path.read-write" }, | |||
| { "APP_SANDBOX_FILE_ACCESS_ABS_RO", "absolute-path.read-only" }, | |||
| { "APP_SANDBOX_FILE_ACCESS_ABS_RW", "absolute-path.read-write" } | |||
| }; | |||
| for (const auto& entry : sandboxTemporaryAccessKeys) | |||
| { | |||
| juce::StringArray values; | |||
| updateField (entry.cMakeVar, values); | |||
| if (! values.isEmpty()) | |||
| result.appSandboxTemporaryPaths.push_back ({ "com.apple.security.temporary-exception.files." + entry.key, | |||
| std::move (values) }); | |||
| } | |||
| result.type = type; | |||
| return result; | |||
| @@ -302,8 +302,7 @@ public: | |||
| for (auto& pp : properties) | |||
| { | |||
| const auto propertyHeight = pp->getPreferredHeight() | |||
| + (getHeightMultiplier (pp.get()) * pp->getPreferredHeight()); | |||
| const auto propertyHeight = jmax (pp->getPreferredHeight(), getApproximateLabelHeight (*pp)); | |||
| auto iter = std::find_if (propertyComponentsWithInfo.begin(), propertyComponentsWithInfo.end(), | |||
| [&pp] (const std::unique_ptr<PropertyAndInfoWrapper>& w) { return &w->propertyComponent == pp.get(); }); | |||
| @@ -418,17 +417,17 @@ private: | |||
| } | |||
| } | |||
| int getHeightMultiplier (PropertyComponent* pp) | |||
| static int getApproximateLabelHeight (const PropertyComponent& pp) | |||
| { | |||
| auto availableTextWidth = ProjucerLookAndFeel::getTextWidthForPropertyComponent (pp); | |||
| auto font = ProjucerLookAndFeel::getPropertyComponentFont(); | |||
| auto nameWidth = font.getStringWidthFloat (pp->getName()); | |||
| if (availableTextWidth == 0) | |||
| return 0; | |||
| return static_cast<int> (nameWidth / (float) availableTextWidth); | |||
| const auto font = ProjucerLookAndFeel::getPropertyComponentFont(); | |||
| const auto labelWidth = font.getStringWidthFloat (pp.getName()); | |||
| const auto numLines = (int) (labelWidth / (float) availableTextWidth) + 1; | |||
| return (int) std::round ((float) numLines * font.getHeight() * 1.1f); | |||
| } | |||
| //============================================================================== | |||
| @@ -74,6 +74,10 @@ public: | |||
| appSandboxValue (settings, Ids::appSandbox, getUndoManager()), | |||
| appSandboxInheritanceValue (settings, Ids::appSandboxInheritance, getUndoManager()), | |||
| appSandboxOptionsValue (settings, Ids::appSandboxOptions, getUndoManager(), Array<var>(), ","), | |||
| appSandboxHomeDirROValue (settings, Ids::appSandboxHomeDirRO, getUndoManager()), | |||
| appSandboxHomeDirRWValue (settings, Ids::appSandboxHomeDirRW, getUndoManager()), | |||
| appSandboxAbsDirROValue (settings, Ids::appSandboxAbsDirRO, getUndoManager()), | |||
| appSandboxAbsDirRWValue (settings, Ids::appSandboxAbsDirRW, getUndoManager()), | |||
| hardenedRuntimeValue (settings, Ids::hardenedRuntime, getUndoManager()), | |||
| hardenedRuntimeOptionsValue (settings, Ids::hardenedRuntimeOptions, getUndoManager(), Array<var>(), ","), | |||
| microphonePermissionNeededValue (settings, Ids::microphonePermissionNeeded, getUndoManager()), | |||
| @@ -173,6 +177,21 @@ public: | |||
| bool isAppSandboxInhertianceEnabled() const { return appSandboxInheritanceValue.get(); } | |||
| Array<var> getAppSandboxOptions() const { return *appSandboxOptionsValue.get().getArray(); } | |||
| auto getAppSandboxTemporaryPaths() const | |||
| { | |||
| std::vector<build_tools::EntitlementOptions::KeyAndStringArray> result; | |||
| for (const auto& entry : sandboxFileAccessProperties) | |||
| { | |||
| auto paths = getCommaOrWhitespaceSeparatedItems (entry.property.get()); | |||
| if (! paths.isEmpty()) | |||
| result.push_back ({ "com.apple.security.temporary-exception.files." + entry.key, std::move (paths) }); | |||
| } | |||
| return result; | |||
| } | |||
| Array<var> getValidArchs() const { return *validArchsValue.get().getArray(); } | |||
| bool isMicrophonePermissionEnabled() const { return microphonePermissionNeededValue.get(); } | |||
| @@ -457,29 +476,36 @@ public: | |||
| { "Temporary Exception: Audio Unit Hosting", "temporary-exception.audio-unit-host" }, | |||
| { "Temporary Exception: Global Mach Service", "temporary-exception.mach-lookup.global-name" }, | |||
| { "Temporary Exception: Global Mach Service Dynamic Registration", "temporary-exception.mach-register.global-name" }, | |||
| { "Temporary Exception: Home Directory File Access (Read Only)", "temporary-exception.files.home-relative-path.read-only" }, | |||
| { "Temporary Exception: Home Directory File Access (Read/Write)", "temporary-exception.files.home-relative-path.read-write" }, | |||
| { "Temporary Exception: Absolute Path File Access (Read Only)", "temporary-exception.files.absolute-path.read-only" }, | |||
| { "Temporary Exception: Absolute Path File Access (Read/Write)", "temporary-exception.files.absolute-path.read-write" }, | |||
| { "Temporary Exception: IOKit User Client Class", "temporary-exception.iokit-user-client-class" }, | |||
| { "Temporary Exception: Shared Preference Domain (Read Only)", "temporary-exception.shared-preference.read-only" }, | |||
| { "Temporary Exception: Shared Preference Domain (Read/Write)", "temporary-exception.shared-preference.read-write" } | |||
| }; | |||
| StringArray sandboxKeys; | |||
| Array<var> sanboxValues; | |||
| Array<var> sandboxValues; | |||
| for (auto& opt : sandboxOptions) | |||
| { | |||
| sandboxKeys.add (opt.first); | |||
| sanboxValues.add ("com.apple.security." + opt.second); | |||
| sandboxValues.add ("com.apple.security." + opt.second); | |||
| } | |||
| props.add (new MultiChoicePropertyComponentWithEnablement (appSandboxOptionsValue, | |||
| appSandboxValue, | |||
| "App Sandbox Options", | |||
| sandboxKeys, | |||
| sanboxValues)); | |||
| sandboxValues)); | |||
| for (const auto& entry : sandboxFileAccessProperties) | |||
| { | |||
| props.add (new TextPropertyComponentWithEnablement (entry.property, | |||
| appSandboxValue, | |||
| entry.label, | |||
| 8192, | |||
| true), | |||
| "A list of the corresponding paths (separated by newlines or whitespace). " | |||
| "See Apple's File Access Temporary Exceptions documentation."); | |||
| } | |||
| props.add (new ChoicePropertyComponent (hardenedRuntimeValue, "Use Hardened Runtime"), | |||
| "Enable this to use the hardened runtime required for app notarization."); | |||
| @@ -3085,6 +3111,7 @@ private: | |||
| options.appGroupIdString = getAppGroupIdString(); | |||
| options.hardenedRuntimeOptions = getHardenedRuntimeOptions(); | |||
| options.appSandboxOptions = getAppSandboxOptions(); | |||
| options.appSandboxTemporaryPaths = getAppSandboxTemporaryPaths(); | |||
| const auto entitlementsFile = getTargetFolder().getChildFile (target.getEntitlementsFilename()); | |||
| build_tools::overwriteFileIfDifferentOrThrow (entitlementsFile, options.getEntitlementsFileContent()); | |||
| @@ -3566,6 +3593,7 @@ private: | |||
| duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue, | |||
| iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue, | |||
| appSandboxValue, appSandboxInheritanceValue, appSandboxOptionsValue, | |||
| appSandboxHomeDirROValue, appSandboxHomeDirRWValue, appSandboxAbsDirROValue, appSandboxAbsDirRWValue, | |||
| hardenedRuntimeValue, hardenedRuntimeOptionsValue, | |||
| microphonePermissionNeededValue, microphonePermissionsTextValue, | |||
| cameraPermissionNeededValue, cameraPermissionTextValue, | |||
| @@ -3576,5 +3604,19 @@ private: | |||
| networkingMulticastValue, iosDevelopmentTeamIDValue, iosAppGroupsIDValue, keepCustomXcodeSchemesValue, useHeaderMapValue, customLaunchStoryboardValue, | |||
| exporterBundleIdentifierValue, suppressPlistResourceUsageValue, useLegacyBuildSystemValue, buildNumber; | |||
| struct SandboxFileAccessProperty | |||
| { | |||
| const ValueTreePropertyWithDefault& property; | |||
| const String label, key; | |||
| }; | |||
| const std::vector<SandboxFileAccessProperty> sandboxFileAccessProperties | |||
| { | |||
| { appSandboxHomeDirROValue, "App sandbox temporary exception: home directory read only file access", "home-relative-path.read-only" }, | |||
| { appSandboxHomeDirRWValue, "App sandbox temporary exception: home directory read/write file access", "home-relative-path.read-write" }, | |||
| { appSandboxAbsDirROValue, "App sandbox temporary exception: absolute path read only file access", "absolute-path.read-only" }, | |||
| { appSandboxAbsDirRWValue, "App sandbox temporary exception: absolute path read/write file access", "absolute-path.read-write" } | |||
| }; | |||
| JUCE_DECLARE_NON_COPYABLE (XcodeProjectExporter) | |||
| }; | |||
| @@ -196,6 +196,10 @@ namespace Ids | |||
| DECLARE_ID (appSandbox); | |||
| DECLARE_ID (appSandboxInheritance); | |||
| DECLARE_ID (appSandboxOptions); | |||
| DECLARE_ID (appSandboxHomeDirRO); | |||
| DECLARE_ID (appSandboxHomeDirRW); | |||
| DECLARE_ID (appSandboxAbsDirRO); | |||
| DECLARE_ID (appSandboxAbsDirRW); | |||
| DECLARE_ID (hardenedRuntime); | |||
| DECLARE_ID (hardenedRuntimeOptions); | |||
| DECLARE_ID (microphonePermissionNeeded); | |||
| @@ -94,23 +94,21 @@ int ProjucerLookAndFeel::getTabButtonBestWidth (TabBarButton& button, int) | |||
| return 120; | |||
| } | |||
| void ProjucerLookAndFeel::drawPropertyComponentLabel (Graphics& g, int width, int height, PropertyComponent& component) | |||
| void ProjucerLookAndFeel::drawPropertyComponentLabel (Graphics& g, int, int height, PropertyComponent& component) | |||
| { | |||
| ignoreUnused (width); | |||
| g.setColour (component.findColour (defaultTextColourId) | |||
| .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.6f)); | |||
| auto textWidth = getTextWidthForPropertyComponent (&component); | |||
| auto textWidth = getTextWidthForPropertyComponent (component); | |||
| g.setFont (getPropertyComponentFont()); | |||
| g.drawFittedText (component.getName(), 0, 0, textWidth - 5, height, Justification::centredLeft, 5, 1.0f); | |||
| g.drawFittedText (component.getName(), 0, 0, textWidth, height, Justification::centredLeft, 5, 1.0f); | |||
| } | |||
| Rectangle<int> ProjucerLookAndFeel::getPropertyComponentContentPosition (PropertyComponent& component) | |||
| { | |||
| const auto textW = getTextWidthForPropertyComponent (&component); | |||
| return { textW, 0, component.getWidth() - textW, component.getHeight() - 1 }; | |||
| const auto paddedTextW = getTextWidthForPropertyComponent (component) + 5; | |||
| return { paddedTextW , 0, component.getWidth() - paddedTextW, component.getHeight() - 1 }; | |||
| } | |||
| void ProjucerLookAndFeel::drawButtonBackground (Graphics& g, | |||
| @@ -81,8 +81,8 @@ public: | |||
| const bool filled, const Justification justification); | |||
| static Path getChoiceComponentArrowPath (Rectangle<float> arrowZone); | |||
| static Font getPropertyComponentFont() { return { 14.0f, Font::FontStyleFlags::bold }; } | |||
| static int getTextWidthForPropertyComponent (PropertyComponent* pp) { return jmin (200, pp->getWidth() / 2); } | |||
| static Font getPropertyComponentFont() { return { 14.0f, Font::FontStyleFlags::bold }; } | |||
| static int getTextWidthForPropertyComponent (const PropertyComponent& pc) { return jmin (200, pc.getWidth() / 2); } | |||
| static ColourScheme getProjucerDarkColourScheme() | |||
| { | |||