How can I change the Dock preferences programmatically? - objective-c

I'm new to Cocoa/macOS programming. I just found out that NSUserDefaults can be used to change application/system settings, like the way the defaults command does.
But I don't know how to cause these settings to update.
e.g., I use NSUserDefaults to set the dock size to 32, and synchronize the setting. But the dock retains the old setting.
Even when I use defaults write com.apple.dock tilesize 32 to change its size, it won't be updated until I logout and login.
Is there any other technology to notify the dock to get the update? I know that System Preferences can do that.
Thank you!

The Mac OS X dock doesn't reload its settings until it is killed and restarted. In the same way that you'd have to change its settings manually via the terminal (defaults write com.apple.dock tilesize 32; killall Dock), you have to do that in code. So, while you've written the defaults portion of the code, you have to write the kill portion:
NSRunningApplication *dock = [NSRunningApplication runningApplicationWithBundleIdentifier:#"com.apple.dock"];
[dock terminate];
If you want to do this without killing the dock, sorry, but you're out of luck. While there might be a hidden API to force the dock to reload its settings on the fly, in all my searching I have never found any hints of how one can do this (there are no notifications posted on the hidden distributed notification center that most applications to interact with one another).

With the magic of the Xcode debugger and some formatted disassembly, I've created this short header file you can paste into your code (GitHub gist is here). Function names are hopefully self-explanatory.
// TO USE THESE INTERFACES, you MUST link against ApplicationServices.framework.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Boolean preferences
extern void CoreDockSetLaunchAnimationsEnabled(bool enable);
extern void CoreDockSetMagnificationEnabled(bool enable);
extern void CoreDockSetAutoHideEnabled(bool enable);
extern void CoreDockSetMinimizeInPlace(bool enable);
// Sets other preferences such as whether the indicators below the app icons are shown
// 'preferenceDict' is a CFDictionary containing a magic key value
// Will require further inspection of Dock.prefpane to find all the keys
// (I haven't noted them down)
extern void CoreDockSetPreferences(CFDictionaryRef preferenceDict);
#ifdef __cplusplus
} // extern "C"
#endif
Preferences updated in this way are reflected instantaneously, because these functions actually messagethe "com.apple.dock" mach service internally.
Have fun!
PLEASE NOTE: These are private system APIs. Any apps submitted to the Mac App Store that use these APIs will be rejected. On another note, if you have no App Store intentions, there's no harm in using these interfaces. They appear to have existed since the very dawn of Mac OS X, and it's highly improbable they'll be disappearing in the near future, if ever.

You can use AppleScript to set some of the properties of the dock by scripting System Preferences, you may want to take a look at seeing if you can take that approach instead? Maybe call some AppleScript from your app?

Related

Is it OK to have Mac OS X application without an NSApplication instance?

In the Info.plist, I have one key "Application is background only" and its value is "Yes".
Most of the code is in C++.
The usual last line
return NSApplicationMain(argc, argv);
in main() is removed. Instead main starts some thread(s), blocks and waits on some condition to exit.
Yes and no.
You can have a process (colloquially an application) without it, and it will work just fine with the Unix side of things, and behave just like any other headless process.
What you cannot have is an Application in the sense of a full-fledged Cocoa Application, because an instance of NSApplication (or a subclass) isn't just a part of your application, in a very real sense the main application object is the application. Things like reading the Info.plist, hooking into the Cocoa Application System, Applescript System, and so on is all done by NSApplicationMain(), making it a requirement of a capital-A Application.
It is possible you could set some of these things up yourself, but I don't know of any ways to do so, and even if I did, I would not recommend it. If you want your program to behave like an application and interact with the Cocoa side of things, return NSApplicationMain(argc, argv); is the wait to end main() .
Yes it is OK. NSApplication is declared in AppKit so it is used only in GUI apps. An app can use Foundation, which does not require NSApplication.

In Obj-C, how to programmatically set the default "open with" property of a file in Mac OS X

In creating some .mov files using Cocoa (Obj-C), I'd like to set them to be opened by default by a specific program, instead of the default. This should be a file level property, I do not wish to change the default program for all files with the same extension. This is to be done from Cocoa itself, as opposed to manually in "context menu">>"Get Info">>"Open With".
There's an undocumented function call that sets this:
// undocumented function call
extern OSStatus _LSSetStrongBindingForRef(const FSRef *inItemRef,
FSRef *inAppRefOrNil);
*If you use this in your application and submit it to the AppStore it will probably get rejected.
As an intermediate between doing it by hand and doing it from Cocoa, there is an Automator action called "Set Application for Files".
I don't think there is a supported way to do it programmatically, but some people have figured out what Finder is doing: Adding a resource of type 'usro' that contains a full path to the application. See for example this discussion. Note: the Resource Manager is deprecated as of 10.8.

How to delete all NSLog's & comments from my Xcode application?

Is there anyway to delete the NSLog lines from the app by any trick/tool? I usually use NSLog's in each and every method to understand the flow of control and to know about the values of the app's variables. I also use lots of comment lines to explain the nature of methods and variables.
At some point these NSLogs and comment lines make the program hard to for me to understand. So I need to keep deleting and recreating them. Is there a way to show/hide them by any trick in Xcode?
Use the global research & replace tool (cmd-shift-f, or Edit, find, Find in Workspace)
Clic on Find, select Replace
Style => Regular expression
For the NSLogs, search
NSLog\(.*\).*$
and replace by a space.
For the comments, search
\/\/.*$
and
\/\*.*\*\/
and replace by a space.
And finish by replacing manually those ones
/* fjeizghrij
eopgfjeipgez
*/
because I don't know how to grab them safely?
EDIT :
At last, beware of this global replace because you won't be able to undo ! Copy/paste your project before, for example. You should use the preview fonctionality of the global replace too, and check each entry.
I'm not sure what the exact reason is why you want to remove the NSLog lines and comments.
if you can read the source code hard, to remove the comments, set the comments colour in the Xcode preferences to same as the background or set their font size to 1 and you won't see them when you read the code. :)
I have no idea for the NSLog, but I'm using the following way to avoid the unwanted logging in the final release.
this is a simple macro:
#ifdef DEBUG
#define DebugLog(...) NSLog(__VA_ARGS__)
#else
#define DebugLog(...) { }
#endif
I'm using the DebugLog(...) as I would use the NSLog(...) normally, and the Xcode is logging only in DEBUG mode, I don't need to remove any log when I create the release version of the app.

Register a global hotkey without support for assistive devices enabled

Using this code, I may register a global event handler:
[NSEvent addGlobalMonitorForEventsMatchingMask: NSKeyDownMask
handler: ^(NSEvent *incomingEvent) {
NSString *chars = [[incomingEvent characters] lowercaseString];
unichar character = [chars characterAtIndex:0];
// do something useful
NSLog(#"keydown globally! Which key? This key: %c", character);
}];
Unfortunately, events get passed along to this monitor, if support for assistive devices is enabled. Without assistive devices being enabled, no events get passed along.
Form the documentation:
Key-related events may only be monitored if accessibility is enabled or if your
application is trusted for accessibility access (see AXIsProcessTrusted).
I wonder, if another method exists, which passes along events without forcing the user to enable specific features of OS X.
While I didn't find a solution in Apple's docs, a solution must exist. E.g. the MAS-downloaded version of Alfred allows to define a hotkey.
Interestingly, Alfred's preferences only shows special keys and points out, that certain special key combinations may not work.
Since I basically want to show / hide a 'global' non-activating panel, I probably should simply prepare a system service. Should I?
You could try creating a Quartz Event Tap, with kCGSessionEventTap as the location.
Quartz Event Services Reference
Sample code from Mac OS X Internals

Calling App Expose In Lion

Is there any way to call App Expose in Lion programmatically, for example on an event tap, etc?
If you don't mind using a TOTALLY UNDOCUMENTED API, which might change at any point without notice:
void CoreDockSendNotification(CFStringRef, void *);
(...)
CoreDockSendNotification(#"com.apple.expose.front.awake", NULL);
Other known arguments are #"com.apple.expose.awake" and #"com.apple.dashboard.awake", which activate Mission Control and Dashboard, respectively. #"com.apple.showdesktop.awake" used to activate Show Desktop, but no longer works on current versions of macOS.
Note that most applications should not use these calls -- these actions are intended to be invoked directly by the user.
Expose does not exist in Lion, it has been merged with Spaces into the Mission Control application.
You can launch Mission Control:
[[NSWorkspace sharedWorkspace] launchApplication:#"Mission Control"];