OSX Quicksilver plugin, Objective C guidance - objective-c

I'm attempting to modify a Quicksilver plugin (screen capture) in Xcode, in order to allow the screencapture destination path to be set to the defaults com.apple.screencapture path value. The plugin currently has the path hardcoded for some reason, so even when you've changed the OSX default path for screen captures, the plugin still sends them to the OSX default, which is the desktop.
I'm a literal noob with Xcode/Objective C, so any info/examples or point in the right direction would be much appreciated.
The screencapture plugin file which needs to be tweaked is the "QSScreenCapturePlugIn.m" file - here's a snippet:
(QSObject *)captureRegion:(QSObject *)dObject{
NSString *destinationPath=[#"~/Desktop/Picture.png" stringByStandardizingPath];
destinationPath=[destinationPath firstUnusedFilePath];
NSTask *task=[NSTask launchedTaskWithLaunchPath:SCTOOL arguments:[NSArray arrayWithObjects:#"-is",destinationPath,nil]];
[task waitUntilExit];
[[QSReg preferredCommandInterface] selectObject:[QSObject fileObjectWithPath:destinationPath]];
[[QSReg preferredCommandInterface] actionActivate:nil];
return nil;
}
I've searched high and low for examples demonstrating how to read from the defaults database, but it seems information is sparse.
Thanks!

I can't believe no one was able to help out with an answer or pointer on this one (or maybe I'm just not patient enough), but thankfully I was able to find the answer while reading through some other answers here on SO - unfortunately I can't reference the Question here, since I lost the page.
Anyway, this will do it:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *screenCaptureLocationString = [[defaults persistentDomainForName:#"com.apple.screencapture"] valueForKey:#"location"];
Amazingly hard to find this info for some reason, but hopefully posting it here will help someone else.

Related

Issues/Questions regarding NSUserDefaults

OK, I admit NSUserDefaults, being a Mac-specific thing (and me being a Mac programmer for the last couple of years), is one of the things I haven't delved into that much... so here are some issues/questions I've come across :
I understand that NSUserDefaults is basically an NSMutableDictionary written as a .plist.
My questions :
Given that I'm running OS X 10.7 (Lion) and having enabled Sandbox, where is my app's .plist file? (I've search in both ~/Library/Preferences/com.example.myapp.plist and ~/Library/Containers/com.example.myapp/Data/Library/Preferences/com.example.myapp.plist but none of these seems valid
I understand that this .plist file is created the first time the app launches, correct?
registerDefaults: is to be used at application launch (e.g. in awakeFromNib) and provide a Dictionary of default values that are immediately stored in the .plist file, and changed only if a different value is set at some point, correct?
When we're setting a specific Key-Value pair, is that pair automatically and immediately saved to the .plist file? And if so, why/when should we use synchronize? (Is using it every single time some value is set an overkill, or should it be reserved for special cases?)
Sidenote : I hope nobody complains about my use of the osx tag. However, I'm really tired of seeing Cocoa Touch / iOS related answers to my (mostly) OSX-related questions. So, here you are... :-)
EDIT : For some really helpful insight on the subject, please have a look at the accepted answer as well as the comments below it.
Answer 1. The home directory is hidden in Lion, so you are not able to enter the path(Without seeing the folder you can not enter inside the folder from Finder). You can use Path Finder to move around your hidden directories.
Answer 2. Not always. There can be multiple plists in a single application. Few gets created at first launch, few at some specific action. Actually it depends when the plist file are created and how to use it.
Answer 3. registerDefaults: registers default values are when the user has not set other values to that property. It will not override whatever the user has stored when the app is opened later. You can use it in anywhere, but as stated it will be used once.
Answer 4. For Mac OSX application there is no performance and overkill issues, however for iOS it has. It is always better to use [[NSUserDefaults standardUserDefaults] synchronize];

Objective-C, programmatically detect i386, or x86_x64 architecture of binary?

Is there a way to programmatically detect/determine if a binary (separate from my application) has been compiled i386, x86_x64, or both? I imagine there is a way (obviously), although I really have no idea how. Any suggestions or advice would be appreciated, thank you.
EDIT: I found an example on the Apple developer website, although it's written in C and setup to be used more as a command line tool. If anyone would know how to implement it into my objective-c code that would be extremely helpful.
Example [C] code: CheckExecutableArchitecture
You can include the mach-o headers and simply load the binary and then check the mach_header. You should read the Mach-O format description from Apple for more info, it includes everything you need: http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
- (void)checkArchitecture {
NSArray *bundleArch = [[NSBundle bundleWithPath:#"/path/to/other/bundle"] executableArchitectures];
}

What's the right way to parse an ISO8601 date in cocoa?

I would like to parse ISO8601 dates in Cocoa, both for iOS 4+ and OSX 10.6+
There are a few questions about this on StackOverflow already, but in my opinion none of them contain good answers. Here's what I think constitutes a good answer:
The answer should point to code with support for ISO8601. This code should compile cleanly under XCode 4 for both iOS 4+ and OSX 10.6+.
The code should support all possible ISO8601 date formats.
Please note that there are many, many possibilities here. Simply answering with one or two format strings for NSDateFormatter is not going to cut it.
The answer should not be this library. That's because it is riddled with dangerous 32-bit assumptions, it's far more complicated than necessary, and it doesn't compile clean with XCode4/Clang. Bottom line: I don't trust it at all!
Thanks, fellow Cocoa-ites. I'm excited to find out if there's a real answer here!
The best way is this library. ☺
I should add a link on that page to the Bitbucket repo, which contains newer source code (including 32-bit and Clang fixes!) and has an issue tracker. If you find any other bugs in it, please file them.
I'd also like to know what you mean by “more complicated than necessary”. Normal usage is very simple:
ISO8601DateFormatter *formatter = [[[ISO8601DateFormatter alloc] init] autorelease]; //Or, if you prefer, create it once in -init and own it until -dealloc
NSDate *parsedDate = [formatter dateFromString:inString];
NSString *unparsedString = [formatter stringFromDate:inDate];
You can switch out dateFromString: or stringFromDate: for one of the longer methods if you need more information (e.g., to preserve the time zone).
If you mean something else, I want to hear it so I can improve the library.

How do I create a Numbers spreadsheet using objective-c?

I'm writing a Cocoa application and I'd like to generate a Numbers spreadsheet from my application using Scripting Bridge. I've generated the Numbers.h file and linked the ScriptingBridge.framework per the directions in Apple's Documentation. Below is the code I'm using to try to simply create a Numbers document and save it.
NSString *path = #"/Users/username/Desktop/Test.numbers";
NumbersApplication *numbers = [SBApplication applicationWithBundleIdentifier:#"com.apple.iWork.Numbers"];
[numbers activate];
NumbersDocument *document = [[[numbers classForScriptingClass:#"document"] alloc] initWithProperties:[NSDictionary dictionaryWithObjectsAndKeys:project.title, #"name", nil]];
[[numbers documents] addObject:document];
[document saveAs:nil in:[NSURL URLWithString:path]];
The code compiles and runs and when I try the saveAs:in: method I get the following error:
-[SBProxyByClass saveAs:in:]: object has not been added to a container yet; selector not recognized [self = 0x2005912e0]
Is there something else I have to do besides adding the document to the [numbers documents] array?
I'm open to using AppleScript, but I'd prefer to using the Scripting Bridge if I can.
Ehh, Numbers scripting with SB; two black arts for the price of one. I would suggest trying to do it in AppleScript first, in order to narrow down the problem a bit.
If it breaks in AS too, then either you've phrased the commands wrongly or there's a problem in Numbers. Since most application scripters use AppleScript, you'll find it easier to get help if you can present code they'll recognise.
If it works, then either your translation of the commands to ObjC is incorrect or there's a problem in SB. Having a working example in AS will provide a starting point for figuring out where things are going wrong.
You might also look into objc-appscript, which provides a more reliable, less obfuscated alternative to SB. Its ASTranslate tool makes it easy to translate working AS commands to ObjC syntax.
Numbers doesn't yet support creation of documents via Applescript. You have to use GUI scripting. The new version of Numbers is supposed to be out Jan 6, 2011 and (hopefully) will fix its severely limited Applescript support.

Getting the POSIX path of the Finder's front window

How can I get the POSIX path of the Finder window that is currently at the top of its window list?
Preferably with some kind of Cocoa framework but I am open for anything.
Background:
I would need this because I want to make a selection of files matching a regex pattern, starting recursively from this path. The idea is to use
[[NSWorkspace sharedWorkspace] subpathsAtPath:thePath]
method to get all the descendants of this path, use "grep" in an NSTask (to avoid packaging a regex support framework) and use
[[NSWorkspace sharedWorkspace] selectFile:aFile inFileViewerRootedAtPath:thePath]
in a loop looping through an NSArray made from the entries returned by grep.
So far, I have looked at NSWorkspace, NSFileManager and NSApplication plus other keyword searches within the Xcode Documentation.
Thanks for checking out my question!
Andre
PS: I am not sure about the grep part, maybe I'll just slap RegexKit Lite in there...
You can probably ask the Finder this via an AppleScript.
This* one-liner works for me:
osascript -e 'tell application "Finder" to set myname to POSIX path of (target of window 1 as alias)'
*a modified version of this.
I'm developing an commercial application that does exactly what you describe and I've been messing with different ways of doing this for over a year now. I'm a newbie developer, so I'm totally open to the idea that there may be a much better way of doing it than my way, but it seems to be stable and work.
I use Apple Script
I get the path of the active document every 3 seconds, but if the active application is the Finder, I get the path of the active window. I do this using Apple Script as described by the accepted answer.
Getting Window List using Carbon
To get the window list of all processes to get the window ID (something Apple Script can't do), I use CGWindowListCopyWindowInfo as detailed in this question:
Getting a unique ID for a window of another application
This presents me an array with all the windows of all processes ordered by which is frontmost. So all I need to do is pluck the first entry from the array. This can also be used to get a screengrab of the front window, if that's helpful to you, as shown in the Son of Grab sample application, which has been invaluable to me as a working example.
Sending Apple Script from Cocoa
When it comes to Apple Script, I've experimented with all 3 that Jim suggests and my conclusion is that each has it's issues, both in terms of stability and flexibility.
Apple Event Manager relies on you
sending raw Apple Events. For me,
this was too much like hard work and
too low level.
Using NSAppleScript I found to be
slow and error prone. It's also
pretty clumsy when you want to send a
whole variety of Apple Script
messages as I do.
When reading about Scripting Bridge,
I got very excited, only to try it
out and be very disappointed. For the
attributes of the windows I was
trying to grab, it didn't even
recognise them as existing. It seemed
to me to be buggy and weird. I expect
if the commands you're sending are
supported by SB, it would be the best
option.
A Fourth Option
I now rely on a fabulous Objective C wrapper around Apple Script called AppScript. It's been around for many, many years, it's stable, the developer is great and most of all it just works.
It has rock solid methods which allow you to send Apple Script and it'll even return errors for you neatly without a whole load of potentially buggy and messy code needing to check for them.
I've been using it for a year now with no problems. If you have any other questions about how to do this, please don't hesitate to comment and I'll try my best to answer them.
Getting the POSIX path represented by the frontmost window in the Finder is going to involve sending Apple Events to the Finder in one way or another.
Your choices include:
Using the Apple Event Manager (or NSAppleEventDescriptor and friends) directly.
Using NSAppleScript or OSAScript objects.
Using ScriptingBridge.
If you are building a Cocoa app, OSAScript is probably the most natural choice.
It is possible get the POSIX path of the Finder's front window by using the ScriptingBridge with Swift
import Foundation
import ScriptingBridge // imports: ScriptingBridge.SBApplication, .SBElementArray, .SBObject
// SBApplication? to SBApplication to FinderApplication
let finder = (SBApplication(bundleIdentifier: "com.apple.finder")!) as FinderApplication
let windowList: SBElementArray = finder.windows!()
guard windowList.count > 0,
let window = windowList[0] as? FinderWindow,
let windowProperties = window.properties,
let windowTarget = windowProperties["target"] as? FinderFolder,
let windowUrlOptionalStr = windowTarget.URL,
let windowUrlSubStr = windowUrlOptionalStr
.removingPercentEncoding?
.dropFirst(7) // "file://" 7 characters
else {
// … handle failure
}
let windowUrl = URL(
fileURLWithPath: String(windowUrlSubStr),
isDirectory: true,
relativeTo: nil)