Man in the Middle System Espionage

October 7th, 2005

Many system services on the Mac rely on command-line tools either stored in public paths or stashed away in hard-to-find places. When I’m curiously poking around on my system (as I’m a bit too often likely to be doing), I often discover one of these master-slave relationships and wonder exactly what’s going on behind the scenes. Usually there are some magic command-line incantations that may or may not be documented for the tool itself. By examining what parts of the system do to take advantage of these command-line entry points, we can potentially come up with ways of facilitating improved behaviors for our own purposes.

As an example, let’s take the screencapture tool. Located at /usr/sbin/screencapture, this is what provides the core functionality for the Cmd-Shift-3 and Cmd-Shift-4 screenshot shortcuts. The process responsible for invoking screencapture is the SystemUIServer. Now, if we are curious about what exactly gets passed to the screencapture tool, we have two obvious choices: intercept the invoker, or intercept the invoked.

Intercepting the invoker can be difficult. Not only do you have to identify which process it is, but you then have to attach to the calling process and attempt to break at the relevant code that executes the target tool. Much easier at times when you just want to take a quick peek is to swap the called tool with a bogus stand in. You don’t even need developer tools! The stand-in is named identically to the real deal, but instead of performing the expected function, it simply echoes the provided arguments to a specified temporary file:

#!/usr/bin/perl
`echo "@ARGV\n" >> /tmp/testDCJ`;

I like to send the arguments to a temporary file instead of printing to the standard output, because some callers will manipulate the forked task such that its standard output is disabled. The nice thing about the simple script above is that it’s completely independent of the tool being replaced. You can keep it around and simply copy it in when you want to peek on something. Don’t forget to move the original aside!

Of course, you could also add another line below the echo to go ahead and call through to the original application, if you want to keep the functionality going while your snooping is in place. For instance, this lets me see exactly what screencapture is being asked to do, without diminishing its ability to do it:

#!/usr/bin/perl
`echo "@ARGV\n" >> /tmp/testDCJ`;
exec("/usr/sbin/screencapture-original", @ARGV);

Once you’ve got this mechanism in place, the possibility of mucking with the passed in parameters is irresistible. Say I don’t like the screenshot sounds, and I tend to take more screenshots of windows than of selected ranges. I just sneak the required parameters in before passing the ones that came from SystemUIServer. I’m done with my snooping so I’ll comment-out the argument printing line until the next time I need to pay attention. This is a simple example, but with a little parsing you can remove unwanted options from the passed-in ARGV array, fine-tune provided filenames, etc:

#!/usr/bin/perl
#`echo "@ARGV\n" >> /tmp/testDCJ`;
exec("/usr/sbin/screencapture-original", '-W', '-x', @ARGV);

The major drawback to leaving your custom file in place of the original is that it’s bound to get clobbered when you update your system and Apple releases a new version of the patched tool. One way to avoid this problem is to keep your man in the middle stashed somewhere outside of the target directory, and point at it with a symbolic link. For instance, if you put your perl script in /Users/Shared, you can perform the patch as follows:

% cd /usr/sbin/
% sudo mv screencapture screencapture-original
% sudo chmod 755 /Users/Shared/ScreenCaptureHack.pl
% sudo ln -s /Users/Shared/ScreenCaptureHack screencapture

Now when Apple comes along with a new screencapture, you can just pop in and repeat those steps to get your custom functionality back (or write a script to do it!).

While we’re talking about screencapture, let me point out some useful, under-published user defaults that control the way SystemUIServer invokes screencapture. I was turned on to the presence of these by a post in MacOSX-Talk, which prompted me to search for and find more detailed explanations on MacOSXHints.com. I found hints for the "type" and "location" keys there, which encouraged me to do my own further snooping (set a breakpoint on CFPreferencesCopyAppValue while re-launching SystemUIServer) and discover the "name" key. Using these keys, you can customize your screen captures to be other than the default PNG "Picture N" on the Desktop (as of Tiger):

% defaults write com.apple.screencapture name "Booyah\!"
% defaults write com.apple.screencapture type "jpg"
% defaults write com.apple.screencapture location "/Users/daniel/Documents/Screen\ Captures"
% killall SystemUIServer

The killall at the end is not necessary for some of the keys, but appears to be necessary for the location.

Now all of my screen captures get stowed away in a tidy subfolder of my Documents folder, they are jpg format for maximum portability, and they have a very silly name.

Many behaviors of Mac OS X are unchangeable, or require heavy hex-editing to alter. But thanks to the frequent use of command-line helpers and defaults-driven behavior, much can be altered with a little snooping and careful patching.

Lazy Parent Views

October 6th, 2005

As a Cocoa programmer, it’s easy to take for granted some of the powerful features of the framework. For instance, autoresizing subviews are a godsend! This is the feature that allows for all of those amazing Interface Builder demos where a few flags are set on the buttons and text views in a window, and they automatically resize and position themselves while their window gets resized willy-nilly by the user.

This cool effect depends on a chain of willingness by NSView to “pass along resize fever.” When a view gets resized, it optionally asks all of its children to resize, and they ask their children to do the same. If a “lazy” parent chooses not to pass the message along to its children, resizing below that node doesn’t take place. Fortunately, NSViews are by default documented to be “ambitious” in this regard.

I was recently confused and perplexed by a failure of my custom views to react to the containing window’s resizing. These views are dynamically added to an empty window just after it has been loaded from a Nib, after which time I expect them to behave according to their preset resizing flags. The empty window has a very simple view hierarchy: just a plain-jane NSView designated to hold the contents of the window (which also has as a superview the window’s frame). Every time I’ve ever created a window in Interface Builder, the resizing has worked as expected. This time it doesn’t.

I double check the resizing flags on my subviews – everything is order. But they don’t respond to resizing the window! When the window resizes, the contentView of the window should resize, and my custom subviews should resize/reposition. That’s how things work! To see whether it’s something funky about my custom view, I decide to test a basic resizing scenario by adding an arbitrary subview to my empty window. I choose an NSButton and place it in the upper-right corner of my window, with its autosizing mask set so that it stays there. I run my program, and lo and behold, now *everything* works, including my custom subviews! The mere presence of the button has cured my woes. But I don’t want to leave a bogus button (or other view) in my window just to work around this problem.

I finally get the bright idea of checking the “autoresizesSubviews” flag of the window’s contentView. If somebody is responsible for setting this flag to YES, I’d like to know who it is, so I can convince them to do it for me! I add a breakpoint on NSView’s setAutoresizeSubviews: method, and relaunch my application (with the test button still in place). I land in AppKit’s nib loading code, where I confirm that parameter is YES:

#0  0x9364be24 in -[NSView setAutoresizesSubviews:] ()
#1  0x9364baf0 in -[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:] ()
#2  0x93637fc4 in loadNib ()

I take out the test button to compare notes against my “broken” case. Sure enough, without my bogus button, the call to setAutoresizesSubviews never gets called! Could it be that NSView is documented as automatically resizing subviews by default, but in fact depends on being set that way by the Nib loader? Since 99% of the views that people care about resizing probably start their lives in Interface Builder with subviews from the get-go, I suppose it’s possible. But the documentation says otherwise! Perhaps the documentation writer made an assumption? This is easy to test, especially since I’m already in gdb. I just create a throwaway NSView object and test the property:

(gdb) p (BOOL)[[[NSView alloc] init] autoresizesSubviews]
$11 = 1 '\001'
(gdb) 

Phew! I breathe a sigh of relief to learn that yes, NSView does conform to its documentation. The setting by the Nib loader is just a paranoid double-check, I guess. So who’s doing me wrong? If a plain-jane NSView comes with autoresizing for free, and my window’s content view is a plain-jane NSView, then somebody must be setting the flag to NO on my behalf. Damn them! But we just observed that the breakpoint on setAutoresizesSubviews: never gets hit for my window’s content view. What’s going on?

It becomes clear to me that my window’s contentView was “just born wrong.” In Cocoa, an object that starts out on the wrong foot was probably archived that way. I become very suspicious of Interface Builder, who was responsible for packing up my NSView but apparently included a nasty surprise in the process of doing so. To confirm my suspicions, I attach with gdb to Interface Builder, set a breakpoint on setAutoresizesSubviews, and resave my Nib. Surely this will reveal the wrong-doing. Bzzt! I have tried and tried but witness no call by IB to setAutoresizesSubviews with a “NO” parameter.

So let’s review the facts. We’ve got a situation where an NSView’s autoresizesSubviews flag turns to NO, but no code on this known planet is culpable (at least through the exposed accessor). If the flag is turning to NO and nobody is calling the accessor, then the odds are that this is an inside job. Why would an NSView turn against itself like that?

I try to get NSWindow out of the picture. There are too many potential bullies on this playground. If the problem is that NSView objects without children get freeze-dried in IB and end up without the autoresizing flag set, then I need to simplify the test case. I create an empty NSView in IB by dragging an NSView to my document window. I don’t do anything to it. Just add an outlet instance var to my window controller, named oBadView. I break on awakeFromNib and examine the simple view from the debugging console:

(gdb) p (BOOL)[oBadView autoresizesSubviews]
$1 = 1 '\001'
Current language:  auto; currently objective-c
(gdb) 

So It’s not a simple NSView issue. I further refine the test (and assure myself of sanity) by dropping a fresh NSWindow instance into my nib, and checking its contentView’s resizesSubviews flag at launch time. It suffers! Well, at least I have a simple way to explain it in the bug report.

I don’t really know where Interface Builder’s windows come from. I know that if they are instantiated in my nib, there is some voodoo freeze-drying that goes on, somewhat analogous to archiving and unarchiving objects with the NSCoder protocol. But adding a new window to my nib causes no break in NSWindow’s init methods. I figure there must be some sort of prototype freeze-dried window that gets instantiated and copied as needed to spawn new windows. But this problem is more insidious than that: if I create a window, and add items to the contentView of that window, I end up with the autoresizes flag set to YES. Now if I remove those items, the autoresizes flag is back to NO, even though I never saw a setAutoresizesSubviews call! I think there must be some kind of private IB “window object” that gets passed around and mucked with. Perhaps this object is what gets freeze-dried and turned magically into a true NSWindow only after my app launches.

I’m not one to give up easily, but in this case, I admit it: this bug has bested me. Considering the workaround is as easy as sending a “setAutoresizesSubviews:YES” message to my window’s content view at nib awakening, I’m happy to let the Interface Builder team at Apple figure out the finer details. (Radar 4290256).

FastScripts Turning Heads

October 5th, 2005

I wrote FastScripts in a fit of anguish. I had finally decided to get around to learning AppleScript, but every time I tried to write a script, I found it was more trouble to run the damn thing than it was to just carry out the steps by hand. Either the script took too long to run, or it messed up my current application’s focus, or I couldn’t find the script to begin with. Apple’s script menu was a step in the right direction, but it infuriated me that it was such a pale reflection of what it ought to be (and even of the much-loved OSA Menu from Mac OS 9).

The first release of FastScripts was a pretty simple knock-off of the Apple script menu, with a few of its biggest shortcomings addressed. I preloaded all of the scripts to eliminate the massive delay I observed in Apple’s menu, and added a few polishing touches to the way the frontmost application changes (or doesn’t change) in response to a script invocation. Finally! I could start learning (and benefiting from) AppleScript.

Over the past few years, FastScripts has grown in functionality and elegance as carefully chosen features have been added to its box of productivity ammunition. For the most part, FastScripts falls into the “simple but effective” category of software, focusing on making script execution in particular as elegant and unobtrusive as possible. For many people, the lack of flair leaves them wondering why they should bother with FastScripts when swiss army knives like QuickSilver and Butler offer cheap or free alternatives. The bottom line is that FastScripts is for scripts. If you like to run scripts, FastScripts is on your wavelength, or at least it tries to be. I have tried the others and without fail I run into some usability or configuration issue that leaves me groaning.

But script nerds aren’t exactly the majority of Mac users, so acclaim for FastScripts is relatively hard to come by. It feels great when an occasional word of praise passes over the airwaves. FastScripts has been lauded in two issues of MacWorld magazine, and occasionally mentioned in the blogs of people I respect a great deal.

Something is in the air this past week, as write-ups and links to FastScripts have turned up in a few different blogging venues.

First, I was very pleased to see a mention in the Sep. 30th linked list section of John Gruber’s Daring Fireball, a blog that I’ve discovered fairly recently but quickly observed to be a great place for thought-out commentary on the Mac.

On Monday, I was reading through Michael Tsai’s blog, when I was surprised to see a write-up devoted completely to FastScripts. I have been reading his blog for many months and am always interested to get his take on things. I breathed a sigh of relief to learn that his take on FastScripts was very positive, having picked up on one of the many subtle features aimed at pleasing my detail-oriented user base.

Finally, FastScripts earned itself a full-page write-up on Tim Gaden’s Hawk Wings. Tim switched to the Mac after a positive experience with Apple’s included Mail client. He’s been so enthusiastic about it, that two years later he’s devoted his personal blog, , to tips and techniques for improving workflow with it. In a recent entry, he describes how FastScripts helps him turbocharge his AppleScripts, in particular in conjunction with Mail.

To everybody who appreciates FastScripts and lets me (or the world) know about it: “Thanks!” You make it all worthwhile.

Interprocess Dragging is a Drag (Sometimes)

September 23rd, 2005

My “Apple Bug Friday” entries today are both related to a really cool feature of Mac OS X: process switching while dragging. As the operating system has evolved, dragging in general has improved. One of my favorite improvements is that while dragging you can now invoke the Cmd-Tab process switcher keys to bring another application to the front. So instead of performing that tedious “line up” to prepare two applications for a drag, you can simply start in one application with the knowledge that you’ll be able to navigate to the other app before your finger releases the dragged item. I often use this, for instance, when sending attachments through Mail:

  1. Locate the attachable files in the Finder.
  2. Select them and start a drag.
  3. Cmd-Tab to activate Mail.
  4. Cmd-N to create a new mail message.
  5. Drop the drag into the content of the new message!

It’s just … downright amazing that you can do this! So where are the bugs? There are two major shortcomings I’ve observed in Apple’s overall fantastic implementation:

Radar 4270813 – Can’t cancel drag outside of initiating application

Normally when you start a drag and end up with second thoughts, you can rely on the effectiveness of the escape key to reject the drag without any worries that you might be accidentally moving a file or object to some new, unfathomable location. For some reason this mechanism simply stops working once you’ve switched to an application other than the app that originated the drag. So, assuming I want to safely cancel a drag that I’ve started and then switched applications for, I have to now navigate back to the original application just to hit the escape key and cancel the drag!

For instance:

  1. Navigate to the Finder and begin dragging a file.
  2. Cmd-Tab to another app, say, Mail.
  3. Hit the escape key. No effect.
  4. Cmd-Tab back to Finder.
  5. Hit the escape key. Drag cancelled!

What a drag! The cancel key should kill the drag safely regardless of where the user has wandered. This is especially problematic if, for some reason, your short-term memory fails you and you can’t remember which application originated the drag.

Radar 4270824 – Can’t completely switch back to drag-originating app

So, once you switch back to the originating app you can hit cancel and breathe a sigh of relief. The problem is, switching back to the originating app while the drag is still in place produces another bug, which is that although the originating app is now “front-most”, none of its IU (not even its menu bar) has yet been brought to the front. So it’s not only confusing, but if you decided you wanted to drag to the originating app after all, you’re out of luck. You’ll have to cancel the drag and start over.

Illustration:

  1. Initiate a drag in the Finder by selecting and dragging a file from a folder list.
  2. While keeping the drag alive with the mouse, Cmd-Tab to another application, say Safari.
  3. Decide you don’t want to drag to Safari after all, and Cmd-Tab back to the Finder.

Result: Although the Finder is somewhat active, none of its windows have come forward, and the menu bar still says “Safari.”

Workaround: If you can glimpse any part of a Finder window’s content, then dragging the item to this content and “moving it around” seems to trigger full activation of the Finder. I imagine it’s similar for other applications.

What a drag! Cmd-Tabbing to an application should always bring it forward.