I have a port of my application, a game, running on Mac OS X. I build with make, and only added a few .mm files to access the necessities from NSApplication, NSWindow and NSOpenGLView.
How do I "convert it" into a proper App as opposed to the current console form? Do I bundle it with something, if so how? Or is it some type of linker setting (as in Windows)? Do I have to build using XCode?
Currently I just "tick" the system, i.e. poll it for events rather than utilizing OS X message pump properly. Could you point me to some basic sample or tutorial which shows me how to do it properly, while still being able to "tick" my own stuff at a fixed frame rate? I say "sample or tutorial", since I am blind when it comes to documentation.
Thanks!
For the creation of a proper Mac application, you'll have to create a Mac OS X bundle. You can find the great documentation on the Apple site, here. You can create them with standard tool (I did write a small python script to create a bundle from a simple .ini file that describe files to pack, and how to construct the Info.plist file).
Basically, an application bundle is just a regular directory with a .app extension, and a fixed structure. The following file are required:
Application.app/
+ Contents/
+ MacOS/
| + <Executable>
+ Resources/
| + <Icon>
+ Info.plist
The Info.plist file is a standard property list file (either in XML or in the old format), that indicate what is the name of the executable file (CFBundleExecutable), what is the name of the icon file (CFBundleIconFile), the bundle type (CFBundleType with a value of APPL), and some other informations (file type supported, version string, development language, ...). Those file is the strict minimum required to have a basic Mac OS X application.
For explanation of how the Mac OS X message pump work, I recommend the reading of this article by Matt Gallagher. He explains how the run message of the NSApplication class is implemented. You can then write this method runOnce that only iterate when there are pending messages. You'll then call this function periodically (it is really similar to the PeekMessage, TranslateMessage, and DispatchMessage sequence on Win32):
- (void)runOnce
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self finishLaunching];
for (;;)
{
[pool release];
pool = [[NSAutoreleasePool alloc] init];
NSEvent *event =
[self
nextEventMatchingMask:NSAnyEventMask
untilDate:nil
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (sender == nil)
break;
[self sendEvent:event];
[self updateWindows];
}
[pool release];
}
Unless you really want to learn a bunch of Mac-specific APIs and XCode, you should just use SDL. It's a cross-platform library that abstracts things like event handling, threading, and multimedia. It includes XCode templates that allow you to build a proper app bundle with minimal changes to your code. Using SDL would mean throwing away most of the Mac-specific code you've written, but it would vastly simplify any future ports and would make it easier to maintain your app for multiple platforms.
Related
Question
Is it possible to determine the location a macOS app was launched from at runtime?
Context
I develop a Safari Extension, and in order for the extension to be enabled the application needs to be present /Applications/. Several users have tried to run the application directly from the DMG file and then complained that the extension doesn't appear in Safari's extension settings. I would like to detect this and alert them that they need to move the file.
You can use NSBundle, specifically the bundlePath property. The documentation says:
The full pathname of the receiver’s bundle directory.
And you can use it something like this:
NSString* bundlePath;
bundlePath = [[NSBundle mainBundle] bundlePath]
NSLog(#"%#", bundlePath);
There's also bundleURL if you want a NSURL to work with instead of a string.
Are there any restrictions as far as saving files when you distribute an app over the Mac App Store?
If I compile and run my app from my computer it works fine - it saves the configuration.
However the version that was downloaded over the Mac App Store is not saving the configuration. I don't even see the config file. Anyone knows what is going on?
This is the code that saves the config:
-(void)saveConfig:(id)plist {
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString: CONFIG_FILE_NAME];
NSData *xmlData;
NSString *error;
xmlData = [NSPropertyListSerialization dataFromPropertyList: plist
format: NSPropertyListXMLFormat_v1_0
errorDescription: &error];
if(xmlData)
{
if (![xmlData writeToFile:path atomically:YES])
NSLog(#"Failed to write the config file onto the hard drive");
}
else
{
NSLog(error);
}
}
You cannot write files to the application bundle directory if you’re targeting the Mac App Store. The bundle is supposed to be immutable.
Consider saving your configuration with NSUserDefaults or, if you truly need a separate file, the officially recommended location is (~)/Library/Application Support. Matt Gallagher wrote a nice post called Finding or creating the application support directory in which he provides a solution that uses standard NSApplicationSupportDirectory followed by the executable name.
Generally, you should assume that your application's assets are read-only. This is true in general, not just for the app store.
If you want to save user settings as a property list, use NSUserDefaults instead of modifying files inside the application. This will "just do the right thing", which is to save preferences to ~/Library/Preferences.
I've made many apps in Xcode before and always their default working directory was the one where the .app file is, so I've accessed the internal data like Whatever.app/Contents/Resources. I know it's probably not the right way, but it has always worked. Anyway, either from a recent Xcode update or for some other reason, their default working folder is now being set to "/". This only happens when I run the .app file from Finder. If I run it from within Xcode, the folder path is correct (I can set that path in the executable options, but it has no effect on what happens when you run the .app directly). Is this a setting somewhere or just the new standard?
For resources, use this cool code:
NSString *path = [[NSBundle mainBundle] pathForResource:#"awesomepic" ofType:#"png"];
You should never depend on PWD with Cocoa. Instead, use Cocoa API's whenever possible. BSD API's should only be used if Apple provided no other way.
globheader.h
static char *appdir;
appcontroller.m
#import "globheader.h"
#implementation AppController
- (void)method {
appdir = [[[NSBundle mainBundle] bundlePath] UTF8String];
}
#end
cppcode.cpp
#include "globheader.h"
int main() {
printf("%s", appdir);
return 0;
}
toastie, why use the application bundle to store data? don't change your .app.
Instread, use the application support folder. You can modify the bytes in that folder with no problems. Read the following post from Matt Gallagher,
http://cocoawithlove.com/2010/05/finding-or-creating-application-support.html
regards,
Is there a unix tool to which I can pass a file and have it tell me where the icon for the file is coming from?
What I am looking for is something like this:
$WhoProvidedIcon /Path/To/file.myex
UTI: com.myapplication.document
PLIST: /path/to/myapplication.app/Contents/Info.plist
ICON: /path/to/myapplication.app/Contents/Resources/BaseDoc.icns
Icons for most applications on Mac OSX are stored within the app bundle themselves. If you right click on the .app and then select Show Package Contents then go to Contents -> Resources you should see the icon in there.
There are at least three different place from which the OS will provide the icon.
In one situation, NSWorkspace can be used to help identify the location:
NSString* uti = [ws typeOfFile:#"/Users/egorr/Desktop/file.vwx" error:&error];
NSLog( #"UTI: %#", uti );
NSLog( #"UTI localized description: %#", [ws localizedDescriptionForType:uti] );
NSLog( #"UTI error description: %#",[error localizedDescription] );
NSURL* utiDeclarationURL = (NSURL*)UTTypeCopyDeclaringBundleURL( (CFStringRef)uti );
NSLog( #"UTI declaration URL: %#", utiDeclarationURL );
NSDictionary* utiDeclaration = (NSDictionary*)UTTypeCopyDeclaration( (CFStringRef)uti );
NSLog( #"UTI declaration: %#", utiDeclaration );
This won't quite get the path to the icon, but it will get one the application from which the declaration came for the UTI.
An icon might also be supplied from a Quick Look plugin and output from qlmanage can be used to determine if this is the case.
A user can, in Finder's Get Info window, set the application they prefer for all files of a given "type". One can set text file types to always open in TextMate, for example. TextMate the app provides custom file icons for all the file types it can save in. After making this change in Finder's get info windows, all files of that type will have an icon that has been provided by TextMate and registered with a UTI. To determine whether this is the case, one would need to look at the launch services database - lsregister -dump
I don't know of any specific tools that provide this information. However, it sounds like you have overlapping extensions. If Finder.app is showing an icon for a file then you should be able to just look in the info window for the file and see what application is listed to open the document by default. Then look at the .plist file for that application to see if it is using your extension. The info window should also say what kind of document it is which could be useful if the application that is supplying the icon has been deleted, or otherwise not listed as the default application.
I have an application I want to bundle (with some additional files) together inside a wrapper application.
Literally the only thing I want to do is have the wrapper application launch a resource file (i.e. as if I'd typed:
/path/to/Resources/Bundled.app/Contents/MacOS/executable
into the terminal.
Make sense? I thought this should be simple, but I caouldn't find a simple way to do this-- my cocoa programming is limited, to say the least.
Thanks in advance!
One way, if the wrapped “application” is just a shell script or something, is Platypus.
If it's an actual application (.app bundle), why does app A do nothing but launch app B? Why have app A at all?
Your outer program can use NSBundle to locate the inner program within the outer program's bundle.
To run the inner program: If it's an application, use Launch Services or NSWorkspace; if it's a command-line tool, use NSTask.
I have a blog post up on this: Turn any shell script into a double-clickable app. The entry mentions "start with an empty app bundle"... which you can get by using the Pashua tool mentioned, if I remember correctly...
Just for the sake of posterity (and if it helps anyone else, here is the full code I used (inside the AppDelegate.m file):
NSString *appName = #"";
NSString *bundledApp = [[NSBundle bundleWithPath:[[NSBundle
mainBundle] pathForResource:appName ofType:#"app"]]
bundlePath];
NSWorkspace *launchApp = [[NSWorkspace alloc] init];
NSLog(#"Launching %s", bundledApp);
[launchApp launchApplication:bundledApp];
[launchApp release];
// Make Launcher terminate (if it serves no other purpose)
[NSApp terminate:nil];