Packaging C binary in Mac OS X Application Bundle - macos

I'm trying to package my binary in a minimalistic app bundle. But I'm seeing some strange behavior with the result.
My bundle has this minimal structure:
$ ls -R HelloWorld.app
Contents
HelloWorld.app/Contents:
Info.plist MacOS PkgInfo
HelloWorld.app/Contents/MacOS:
helloworld
helloworld is a C binary compiled from:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
while (1) {
printf("Hello world!\n");
sleep(2);
}
return 0;
}
Info.plist contains:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>helloworld</string>
<key>CFBundleIdentifier</key>
<string>com.litl.helloworld</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>HelloWorld</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>20</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>LSUIElement</key>
<true/>
<key>LSBackgroundOnly</key>
<true/>
</dict>
</plist>
Now for the strange behavior. When I run
open ./HelloWorld.app
The command hangs for about 30s. After that I can confirm that the helloworld binary is running. However its standard output does not show up in Console.app. If I launch this bundle programmatically (NSWorkspace sharedWorkspace] launchApplicationAtURL...) the call succeeds, but the binary exits immediately (I can see in the console it exited with error code 2).
This is on OS X 10.9.2.
What am I doing wrong?

You need to register with Cocoa to mark your application as responsive and 'ready'. If you would enable the dock icon, it means that it stops to bounce. In your case, if you hide the icon from the dock, you still need to register with Cocoa.
You can do that e.g. by creating a NSApplication class. See here for some low level deails.

Related

How do I acces files on iCloud from C# console app running on macOS

In an iOS app built by colleagues of mine, some files are generated and put in an iCloud container.
Now I am tasked to get hold of these files from some service running on the backend.
If necessary on a Mac Mini installed in our server room.
I am a C# developer so I prefer the dotnet way, which should be entirely possible with net6.0.
dotnet new console
on My MacBook creates the Hello World! sample, which runs great on macOS.
To access files from iCloud containers, I need the NSFileManager.DefaultManager from the Foundation namespace.
This can be achieved in one of two ways AFAIK.
Microsoft.MacOS by changing the <TargetFramework> from net6.0 to net6.0-macos, or
Xamarin.Mac by including a reference to
/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/64bits/full/Xamarin.Mac.dll
In either case, I need to add an Info.plist file to the project.
Using the 1st approach, the console app crashes without the console ever showing 'Hello World!'
The latter approach does show 'Hello World!', but then throws an exception:
System.ArgumentNullException: Value cannot be null.
at System.Threading.Monitor.ReliableEnter(Object obj, Boolean& lockTaken)
at System.Threading.Monitor.Enter(Object obj, Boolean& lockTaken)
at ObjCRuntime.Runtime.TryGetNSObject(IntPtr ptr, Boolean evenInFinalizerQueue) in /Library/Frameworks/Xamarin.Mac.framework/Versions/8.12.0.2/src/Xamarin.Mac/ObjCRuntime/Runtime.cs:line 1374
at ObjCRuntime.Runtime.GetNSObject[T](IntPtr ptr) in /Library/Frameworks/Xamarin.Mac.framework/Versions/8.12.0.2/src/Xamarin.Mac/ObjCRuntime/Runtime.cs:line 1426
at Foundation.NSFileManager.get_DefaultManager() in /Library/Frameworks/Xamarin.Mac.framework/Versions/8.12.0.2/src/Xamarin.Mac/Foundation/NSFileManager.g.cs:line 1165
at Program.<Main>$(String[] args) in /Users/sbc/Projects/Console1/Program.cs:line 8
I am new to dotnet on Mac and would rather avoid it, but if it is the only way to get to files from an iCloud container, it might be the way to go.
Here is my project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
<Selfcontained>true</Selfcontained>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Xamarin.Mac.dll">
<HintPath>/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/64bits/full/Xamarin.Mac.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
Here is my Program.cs:
// See https://aka.ms/new-console-template for more information
using Foundation;
Console.WriteLine("Hello, World!");
try
{
var manager = NSFileManager.DefaultManager;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Here is my Info.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>.NET 6 Console Mac Sample</string>
<key>CFBundleIdentifier</key>
<string>com.mycompany.console1</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>12.3</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSHumanReadableCopyright</key>
<string>MyCompany</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>StarCommissioning</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</plist>

LaunchAgent cannot access macOS "protected" folders

I have a shell script which does this:
#!/bin/bash
ls -la "$HOME/Pictures/Photos Library.photoslibrary"
When I run this script in the shell it works fine. If I define a LaunchAgent (under $HOME/Library/LaunchAgents) which executes this script, I get the following error message:
ls: Photos Library.photoslibrary: Operation not permitted
My real script is invoking HashBackup (hb) which results in the same kind of error on all those "protected" folders (pictures, address book, etc...). But I was able to reproduce with a simple ls.
What am I supposed to do to fix this?
This is on macOS 10.14.6.
Thanks
Thanks to Gordon comment, I was able to follow the steps and fix my issue. The steps that actually worked for me are these ones.
For the sake of a more complete solution, here is a small CMake based solution:
main.cpp
#include <iostream>
int main()
{
std::cout << "Wrapper app which is authorized for full disk access so that the shell script can run with the same permission" << std::endl;
return 0;
}
backup_argon.sh
#!/bin/bash
# this is just a test... it should invoke hb instead
ls -la "$HOME/Pictures/Photos Library.photoslibrary"
Info.plist.in
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>LSUIElement</key>
<true/>
</dict>
</plist>
CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
set(VERSION 1.0.0)
project(HashBackupLaunchAgent VERSION "${VERSION}")
set(CMAKE_CXX_STANDARD 17)
set(MACOSX_BUNDLE_BUNDLE_NAME "HashBackupLaunchAgent")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.pongasoft.HashBackupLaunchAgent")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VERSION}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${VERSION}")
set(MACOSX_BUNDLE_COPYRIGHT "2021 pongasoft")
add_executable(HashBackupLaunchAgent MACOSX_BUNDLE main.cpp backup_argon.sh)
set_target_properties(HashBackupLaunchAgent PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/Info.plist.in")
set_source_files_properties(backup_argon.sh PROPERTIES MACOSX_PACKAGE_LOCATION MacOS)
Compiling this project will result in an application (HashBackupLaunchAgent.app) which I copied under /Applications.
I then gave Full Disk Access privilege to this app under System Preferences/Security & Privacy/ Privacy
I then have a LaunchAgent with the following definition:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ypujante.hashbackup.argon.plist</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/bin:/usr/bin:/usr/local/bin</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/Applications/HashBackupLaunchAgent.app/Contents/MacOS/backup_argon.sh</string>
</array>
<key>StandardOutPath</key>
<string>/Users/ypujante/Library/Logs/HashBackup/argon.log</string>
<key>StandardErrorPath</key>
<string>/Users/ypujante/Library/Logs/HashBackup/argon.log</string>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>7</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
</array>
</dict>
</plist>
Note how the launch agent definition invokes the script inside the app not the app itself. And it works: the script inherits the full disk access privilege given to the app.

MacOS Metal: Failing to capture GPU Frame from command line app

I'm trying to programmatically capture GPU frames using MTLCaptureManager in a command line application.
So far, the capture manager fails to support the MTLCaptureDestinationGPUTraceDocument destination.
I tried to create a very minimal repro case using XCode :
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager];
if (![captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument])
{
NSLog(#"********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************");
}
else
{
NSLog(#"captureManager support is fine");
}
}
return 0;
}
When run with XCode, it seems to be willing to work : the output is :
2020-09-02 16:25:59.712217+0200 testMetalCapture[20095:416447] Metal GPU Frame Capture Enabled
2020-09-02 16:25:59.712503+0200 testMetalCapture[20095:416447] Metal API Validation Enabled
2020-09-02 16:26:00.669092+0200 testMetalCapture[20095:416447] captureManager support is fine
Program ended with exit code: 0
But when I archive the build result, and run from a terminal, it fails :
2020-09-02 16:32:57.607 testMetalCapture[20126:419837] ********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************
Is there any runtime environment I could reproduce in terminal to get the MTLCaptureManager working ?
(Environment is XCode 11.6 + MacOS 10.15 Catalina)
From what I understood (I could not find any official documentation) :
MTLCaptureManager needs an authorization from the Info.plist: the MetalCaptureEnabled should set to YES.
The proper way is to bundle the command line application with such a plist
This is quite involved, see for example Building OSX App Bundle
I found by accident that MTLCaptureManager also works if there's an Info.plist in the same directory as the command line application.
This Info.plist can be almost empty, like this :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
With this simple setup, I can run my test program (and my real one too)
% ./testMetalCapture
2020-10-02 15:53:08.507 testMetalCapture[28559:686864] Metal GPU Frame Capture Enabled
2020-10-02 15:53:08.523 testMetalCapture[28559:686864] captureManager support is fine
(I'm currently using MacOSX 10.15.6, it may break in the future)
The trick from #rotoglup was working for me, until it wasn't anymore: some buffers were full of zeros, and no image appeared in the 'screenshots' inside the metal debugger. I don't know if this was because of an update of the OS or of XCode or something else.
I still needed an Info.plist with this content, as #rotoglup suggested:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
but I also needed to set the METAL_DEVICE_WRAPPER_TYPE env variable to 1 in the terminal that launches the command line app:
export METAL_DEVICE_WRAPPER_TYPE=1
With this, buffers are filled up correctly, and I get non-empty screenshots.

launchd.plist runs every 10 seconds instead of just once

I have been setting up a launchd.plist XML that is run every time a specific USB device is mounted. I followed the instructions on the xpc_events(3) man page and it is running the application whenever the device is mounted.
The problem I'm having is that the application is run again and again every 10 seconds as long as the device is still mounted. How can I set it up so it only runs once when the device is inserted in the USB port?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myapp.agent</string>
<key>Program</key>
<string>/Applications/MyApp.app/Contents/MacOS/MyAgent</string>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>2316</integer>
<key>idProduct</key>
<integer>4096</integer>
<key>IOProviderClass</key>
<string>IOUSBDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
</dict>
</dict>
<key>com.apple.notifyd.matching</key>
<dict>
<key>com.apple.interesting-notification</key>
<dict>
<key>Notification</key>
<string>com.apple.interesting-notification</string>
</dict>
</dict>
</dict>
</dict>
</plist>
I wrote a tutorial on this with detailed instructions and example files for triggering an arbitrary executable or shell script by the connection of an external device (usb/thunderbolt) to a Mac computer, without the respawning problem.
Like the authors approach, it relies on Apple's IOKit library for device detection and a daemon for running the desired executable. For the daemon to not be triggered repeatedly after connecting the device, a special stream handler (xpc_set_event_stream_handler) is used to "consume" the com.apple.iokit.matching event, as explained in the post by #ford and in his github repo.
In particular, the tutorial describes how to compile the xpc stream handler and how to reference it together with the executable in the daemon plist file and where to place all the relevant files with correct permissions.
For the files, please go here. For completeness, I have also pasted their content below.
Run shell script or executable triggered by device detection on a mac
Here I use the example of spoofing the MAC address of an ethernet adapter when it is connected to the Mac. This can be generalized to arbitrary executables and devices.
Put your shell script or executable into place
Adapt the shell script spoof-mac.sh
#!/bin/bash
ifconfig en12 ether 12:34:56:78:9A:BC
to your needs and
make it executable:
sudo chmod 755 spoof-mac.sh
Then move it into /usr/local/bin, or some other directory:
cp spoof-mac.sh /usr/local/bin/
Building the stream handler
The stream handler xpc_set_event_stream_handler.m
// Created by Ford Parsons on 10/23/17.
// Copyright © 2017 Ford Parsons. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <xpc/xpc.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
NSLog(#"%s", event);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
is universal (no need to adapt) and can be built on a mac command line (with xcode installed):
gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m
Let's place it into /usr/local/bin, like the main executable for the daemon.
cp xpc_set_event_stream_handler /usr/local/bin/
Setup the daemon
The plist file com.spoofmac.plist contains the properties of the daemon that will run the executable on device connect trigger.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UserName</key>
<string>root</string>
<key>StandardErrorPath</key>
<string>/tmp/spoofmac.stderr</string>
<key>StandardOutPath</key>
<string>/tmp/spoofmac.stdout</string>
<key>Label</key>
<string>com.spoofmac.program</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/xpc_set_event_stream_handler</string>
<string>/usr/local/bin/spoofmac.sh</string>
</array>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>32902</integer>
<key>idProduct</key>
<integer>5427</integer>
<key>IOProviderClass</key>
<string>IOPCIDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
<key>IOMatchStream</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
It contains information for identifying the device you want to base your trigger on, like idVendor, idProduct, IOProviderClass. These can be figured out in the System Information App on your mac.
Convert the hex identifiers to integers before inserting into the plist file (for example using int(0x8086) in python).
IOProviderClass should be either IOPCIDevice (Thunderbolt) or IOUSBDevice (USB).
The other relevant entry in the plist file is the location of xpc_set_event_stream_handler and the executable.
Other entries include the location of standard output (log) files and the executing user.
Since MAC spoofing requires root privileges, we put com.spoofmac.plist into /Library/LaunchDaemons:
cp com.spoofmac.plist /Library/LaunchDaemons/
not into a LaunchAgents folder. Launch agents ignore the UserName argument.
Insure that the owner of the file is root:
sudo chown root:wheel /Library/LaunchDaemons/com.spoofmac.plist
Launch the daemon
Activate the daemon:
launchctl load /Library/LaunchDaemons/com.spoofmac.plist
and you are good to go.
Unloading is done using launchctl unload.
AIUI your application must call xpc_set_event_stream_handler to remove the event from the queue. You might also have to add <key>KeepAlive</key><false/> to the .plist, but I'm not sure about that.
I am trying to use something like this:
#include <xpc/xpc.h>
#include <unistd.h>
#include <asl.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
return 1;
}
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: starting");
xpc_set_event_stream_handler("com.apple.iokit.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t event) {
const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);
uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: received event: %s: %llu", name, id);
execv(argv[1], argv + 1);
});
dispatch_main();
return 0;
}
So a script which consumes the event and runs the script passed as an argument.
This works for me:
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
full source code here

Creating a bundle - What's going wrong?

I've got a relatively simple one here. I'm making bundles to live inside the Resources folder of my application (and possibly in the Application Support folder). These bundles will contain template information for the data the application handles. I've constructed a bundle, with extension "booksprintstyle", and the directory structure is up to spec. I have an Info.plist all set and I think I've filled in all the values I need to. Do I need to change something in my App to have these folders-with-extensions recognized as bundle files, or am I missing something in my bundle structure? I noticed that some bundles have a file called PkgInfo; is that important?
Below is the Info.plist from my bundle.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleGetInfoString</key>
<string>1.0, Copyright © 2009 Joey Lange</string>
<key>CFBundleIdentifier</key>
<string>net.atherial.books.exporter.printingpress.printstyle</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Books Print Style - Generic</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleDisplayName</key>
<string>Books Print Style - Generic</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2009 Joey Lange</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
Do I need to change something in my App to have these folders-with-extensions recognized as bundle files…
Yes. Export a UTI declaration in your app's Info.plist for the type of these bundles. You'll declare the UTI as conforming to com.apple.package. See Understanding Uniform Type Identifiers for more.
Below is the Info.plist from my bundle.
That isn't relevant. The necessary declaration goes in your application bundle, not your document/plug-in bundles.
I noticed that some bundles have a file called PkgInfo; is that important?
No.

Resources