How can I set app as default on Mac OS? - objective-c

Good day, i want to set my application default for a file type with coocoa framework. Now i write next code:
bool setappasdefualt()
{
OSStatus returnStatus = LSSetDefaultRoleHandlerForContentType(CFSTR(".txt"), kLSRolesAll, (CFStringRef) [[NSBundle mainBundle] bundleIdentifier]);
if (returnStatus != 0)
{
NSLog(#"Got an error when setting default application - %ld", returnStatus);
return false;
}
return true;
}
but after execution it get error 50. What i do wrongly?

".txt" is a file extension, not a uniform type identifier (UTI).
You need to use a proper UTI, e.g. CFSTR("public.plain-text")
User Guillaume wrote a nice little function to convert file extensions to UTI's, you can see it here, named "UTIforFileExtension:" (and you should upvote his answer if it helps you :-)

Related

Getting Bundle Identifier for KEXT from .kext Directory Programmatically

I'm building a kernel extension and having a .kext directory for it.
From another library API, I'm using KextManager APIs to load this kext into kernel.
Everything looks fine, here is the code piece that I'm loading the kext with:
CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, "awesome.kext", kCFStringEncodingUTF8);
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, true);
OSReturn result = KextManagerLoadKextWithURL(url, NULL);
It works great, however I need to know my bundle identifier to have control socket connection, and maybe unload the kext with KextManagerUnloadKextWithIdentifier(kextId) API later.
So I'm aware of XXX.kext/Contents/Info.plist which contains CFBundleIdentifier key with bundle identifier but I need some way of getting it programmatically (some API?) instead of reading Info.plist file and parsing it.
I also tried to replace CFBundleIdentifier string value with something else and load into kernel, it still works fine, so it looks like Info.plist is not reliable anyway.
Is there anything related? Any suggestions? Thanks! :)
I discovered 2 useful approaches recently, both based on KextManagerCopyLoadedKextInfo API.
void handle_kext(const void* key, const void* value, void* context)
{
// OSBundlePath - CFString (this is merely a hint stored in the kernel; the kext is not guaranteed to be at this path)
CFStringRef bundle_path_key = CFStringCreateWithCString(kCFAllocatorDefault, "OSBundlePath", kCFStringEncodingUTF8);
const char* bundle_id_cstring = CFStringGetCStringPtr(key, kCFStringEncodingUTF8);
const char* bundle_path_cstring = CFStringGetCStringPtr(CFDictionaryGetValue(value, bundle_path_key, kCFStringEncodingUTF8);
// #1 Approach: Compare your 'I-Want-This-BundleID' with loaded kext path
// #2 Approach: Compare your 'I-Want-This-BundlePath' with loaded kext ID
}
void some_func()
{
CFDictionaryRef loaded_kexts = KextManagerCopyLoadedKextInfo(NULL, NULL);
CFDictionaryApplyFunction(loaded_kexts, handle_kext, NULL);
}

How can I suppress the autosave “The file has been changed by another application” alert?

I have a NSDocument subclass that presents a text document from disk. I’m trying to make it refresh automatically on detecting file changes on disk. I’ve overridden -presentedItemDidChange like this:
- (void)presentedItemDidChange
{
[super presentedItemDidChange];
// Ignoring bundles and error-handling for the moment.
NSData *newData = [NSData dataWithContentsOfURL:self.presentedItemURL];
self.textView.string = [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding];
}
The UI refreshes fine when the file is changed in another application. The problem is, I get this dialog when I try to save the document in my application after it is modified by another app:
I kind of have an idea why this happens (not sure whether it’s correct): The modification time of the document is later (because it’s modified by another application) than the latest saved version in my app. But can I notify the autosaving system that I have done something with it and let it go away? Or am I doing things wrong when I refresh the document, and I should do it some other way to handle document versions correctly? I need to consider both external applications support or do not support autosave.
Thanks in advance.
#uranusjr's answer pointed me in the right direction -- only revertDocumentToSaved: wasn't exactly the right place.
override func presentedItemDidChange() {
dispatch_async(dispatch_get_main_queue()) {
self.reloadFromFile()
}
}
func reloadFromFile() {
guard let fileURL = self.fileURL else { return }
do {
try revertToContentsOfURL(fileURL, ofType: "YOUR TYPE HERE IF NECESSARY")
} catch {
// TODO handle error
print(error)
}
}
This simply reloads the file. readFromURL(url:, ofType:) (or the NSData/file wrapper based variants) is called and you can re-create your data structures from there.
Stumbled across the solution today (finally). You can “cheat” OS X into not warning about this by reverting the document (but not the file itself) before actually updating the internal data structure:
// Somehow read the updated data.
NSString *content = ...;
// Revert the document.
// This will discard any user input after the last document save,
// so you might want to present some UI here, like an NSAlert.
[self revertDocumentToSaved:self];
// Update the internal state.
self.content = content;
Now OS X will be happy when you save the document.

Open file with own application written in objective-c

I would like to know how can I open a file with my OS X application, which I wrote in Objective-C. I registered the file types in Info.plist and I have application:openFile: in my code. I did everything by this post, which was marked as solved.
The problem is that this works only if I drag and drop my file on my application while it is running. But it doesn't work if I just double click on my file. It starts my application, but not as it would start if I would drag and drop. So the code which is in application:openFile: doesn't run when double-clicked, but only when I drag and drop my file.
EDIT:
Some more detail about my code, and what I am trying to achieve.
I created a wrapper application for an other app. Let's call the other app the "HelperApp.app". This HelperApp is inside the /Contents/ folder of my wrapper app. With the wrapper app I specified a new file type, let's call it ".ha" in the Info.plist file. This file contains some argument commands. What I try to achieve, that when a user clicks on a file which is a ".ha" file, then my wrapper app reads in the argument from this file and sets it for the HelperApp, then starts the HelperApp. This HelperApp is opening different things depending on the argument it gets. Below you can check my code.
I have an AppDelegate.h and an AppDelegate.mm by default how the newest Xcode creates it. I added this line to my AppDelegate.h, just before the "#end":
- (BOOL)processFile:(NSString *)file;
I have these functions in my AppDelegate.mm:
#import "AppDelegate.h"
#import "ArgumentParser.h"
#implementation AppDelegate
- (void)dealloc
{
[super dealloc];
}
- (BOOL)application:(NSApplication *)WrapperApp openFile:(NSString *)filename
{
return [self processFile:filename];
}
- (BOOL)processFile:(NSString *)file
{
NSLog(#"The following file has been dropped or selected: %#",file);
std::string path = [file cStringUsingEncoding:[NSString defaultCStringEncoding]];
ArgumentParser parser = ArgumentParser();
parser.getArgumentfromFile(path);
parser.setArgumentinFile(); // <== This is the "corrupted" function
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *helperAppPath = [[mainBundle bundlePath]
stringByAppendingString:#"/Contents/HelperApp.app"];
[[NSWorkspace sharedWorkspace] launchApplication:helperAppPath];
return YES;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
}
The corrupted function - setArgumentinFile():
void ArgumentParser::setArgumentinFile() {
std::string content = ""; // The file content
std::fstream file;
file.open("HelperApp.app/Contents/Wrapper/HelperApp.app/Contents/Info.plist");
// Open the file and modify the arguments
if(file.is_open()) {
std::stringstream stream;
stream << file.rdbuf();
std::string line = "";
bool isIt = false;
while(getline(stream, line)) {
// This line is the argument list, which needs to be modifyed
if(isIt) {
int index = (int)line.find_last_of("<");
std::string start = line.substr(0, index);
std::string end = line.substr(index, std::string::npos);
std::string argument_list = start + " " + _argument + end;
content += argument_list + '\n';
isIt = false;
}
// Save the rest of the file so we can overwrite it
else {
content += line + '\n';
}
// Next line is the argument list
if(line.find("WineProgramArguments") != std::string::npos) {
isIt = true;
}
}
file.flush();
file.close();
}
else {
file.flush();
file.close();
throw std::runtime_error("File isn't opened");
}
file.open("HelperApp.app/Contents/Wrapper/HelperApp.app/Contents/Info.plist", std::ios::out);
// Open the file and overwrite it with the modifyed argument
if(file.is_open()) {
file << content;
file.flush();
file.close();
}
else {
file.flush();
file.close();
throw std::runtime_error("File isn't opened");
}
}
If I comment out the above function from the processFile function in AppDelegate, then everything works "smoothly". I mean the wrapper app starts and it starts the HelperApp with default arguments. So here should be the error...
If you've implemented -application:openFile:, it should be called when you double-click a file of the type that you've registered. You say that the app launches, so the OS is trying to use your app to open the file. Here's a useful note from the documentation:
If the user started up the application by double-clicking a file, the
delegate receives the application:openFile: message before receiving
applicationDidFinishLaunching:. (applicationWillFinishLaunching: is
sent before application:openFile:.)
So, if you're doing anything in -applicationDidFinishLaunching: that has to be done before you open any files, that could be your problem. Consider moving your app initialization code to -applicationWillFinishLaunching:.
I've figured it out. When you double-click on a file icon, the application will launch itself, other things done correctly. But the application that responds to your action is not necessarily the one that you built for the last time. Probably, an old copy of your application is responding. Take a look at Library > Developer > Xcode > DrivedData. You should see many folders for your application. You can locate your application folders by right-clicking and choosing Shown In Finder after build one. Trash them all, and build a new application. Then double-click and see what happens now.
The problem was, that I gave the wrong path in my function. This path worked if I started the app from Xcode, but did not if I started the app by itself.
Here is the post which solved my problem!
right-click vs. double-click to open a file behave differently!
Apple Docs:
If the user started up the application by double-clicking a file, the delegate receives the application:openFile: message before receiving applicationDidFinishLaunching:. (applicationWillFinishLaunching: is sent before application:openFile:.)
The Apple Docs leave out a vital piece of info...
I had assumed that a right-click -> 'Open With'
operation in Finder would be the same as a double-click.
Its NOT!
application:openFile: happens AFTER applicationDidFinishLaunching: in this case!
Was scratching my head for an hour on this one.

Change Authorization Dialog shown by AuthorizationCreate()

Looking through Apples BetterAuthorizationSample and further Derivatives( http://www.stevestreeting.com/2011/11/25/escalating-privileges-on-mac-os-x-securely-and-without-using-deprecated-methods/ )
I am trying to make a small change to the application and gain better understanding of the whole Security & ServiceManagement framework.. Therefore I proceeded to add an a button which removes the installed Job through the inverse of SMJobBless - SMJobRemove(). Straightforward however the AuthorizationCreate() call displays a dialog that states and requests permission to install a helper and not remove it.
That's the dialog I get (by using kSMRightModifySystemDaemons). As you can see it says that my app tries to add a new helper tool. Which will confuse my users because the app actually tries to remove the installed helper tool.
I'm seeking to find knowledge on how this dialog is changed to reflect my actual action (Job Removal), There are also several other apps which seem to completely customize the dialog - showing their own Custom Label and Buttons..
BOOL doRemoveSystemTool(NSString* label, NSError** error)
{
BOOL result = NO;
AuthorizationItem authItem = { kSMRightModifySystemDaemons, 0, NULL, 0 };
AuthorizationRights authRights = { 1, &authItem };
AuthorizationFlags flags = kAuthorizationFlagDefaults |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights;
AuthorizationRef authRef = NULL;
//Obtain authorization
OSStatus status = AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &authRef);
if (status != errAuthorizationSuccess)
{
NSLog(#"Failed to create AuthorizationRef, return code %ld", (long)status);
} else
{
//We have authorization so proceed with removing the Job via SMJobRemove
result = SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef)label, authRef, YES, (CFErrorRef *)error);
}
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
return result;
}
I have experimented with the authItem changing to kSMRightModifySystemDaemons from kSMRightBlessPrivilegedHelper but all this did was change the dialogue to display 'Add' instead of 'Install'
Would greatly appreciate some assistance here...
I haven't used this before but found your question interesting so I did a little reading of Apple's documentation and based on that I wonder if setting up the environment with a kAuthorizationEnvironmentPrompt would do what you want?
From AuthorizationTags.h:
The name of the AuthorizationItem that should be passed into the environment
when specifying a invocation specific additional text. The value should be a
localized UTF8 string.
You'd create an AuthorizationItem with this and then an AuthorizationItemSet containing that, and then pass the set into the AuthorizationCreate call for the environment: parameter.
I'd try that.
The other idea I had reading the documentation was to have a command line tool that does the remove and authorize the execution of the command line tool ("SomethingSomethingHelper") which might be less confusing to the user (so using AuthorizationExecuteWithPrivileges or kAuthorizationRightExecute or whatever).

multiple AuthorizationExecuteWithPrivileges

My application needs authentication to write to the hosts file. I can do this using the following bit of code, that I call. My problem is that when sometimes the user will need to make this change more than once in that instance of the program - the warning dialog asking for the password only appears the first time this is called, and even though the function is called again later, the password request does not show. Can anyone shed any light into this? thanks.
- (void)someFunction {
AuthorizationRef authorizationRef;
OSStatus status;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults, &authorizationRef);
//Run the tool using the authorization reference
char *tool = "/bin/mv";
char *args[] = { "-f", "/tmp/hosts", "/etc/hosts" };
FILE *pipe = NULL;
status = AuthorizationExecuteWithPrivileges(authorizationRef,
tool, kAuthorizationFlagDefaults, args, &pipe);
}
If you want to force re-authentication you should call
status = AuthorizationFree (authorizationRef, kAuthorizationFlagDestroyRights);
after AuthorizationExecuteWithPrivileges