Monitor Log Using Console.app

When I’m working on a Linux desktop, I will frequently open a new shell window with tail -f on a log file I need to watch, such as my JBoss log. When I switched to my glorious Mac Pro I did the same thing, but it wasn’t exactly what I wanted.

I discovered that you can execute Console.app from the command line, so I have created a Bash alias that I run when I need to. Here’s what I put in my ~/.alias file:

alias jbtail='/Applications/Utilities/Console.app/Contents/MacOS/Console /opt/jboss/server/default/log/server.log &'

(Make certain all of that is on one line!) Obviously you should change the path to the log to be something you want to monitor. You can change the alias name to something else if you like. After editing the file, either logout or type . ./.alias (notice the dots) and that will load up the alias. Now you can type jbtail (or whatever you chose to call it) and Console.app will open for you watching changes to that log file.

What’s really nice about this method as opposed to just using Terminal.app is that Console.app remembers things like window geometry, fonts and, most importantly, which monitor it last ran on. This is really nice for people like me who have two monitors. I always want this log to show up on my second monitor, and once I’ve placed it there, future invocations of my alias will open it right where I left it.

If you already have a .bash_profile or .profile file in your home directory, look for a line that looks like this

test -r ~/.alias && . ~/.alias

If you see that, you’re all set. If not, adding it will ensure that your aliases get loaded when you next login. You could add the alias=... line directly to .profile or .bash_profile if you like, and skip the .alias file altogether. I have several aliases and I like to keep them in their own file.

No Flash By Default In Fedora 8? Huh?

I saw that Fedora 8 had been released, so I thought I’d install it inside Parallels and check it out. After a long install, I booted up in 800×600 mode… Lovely. I googled to find out how to change the screen resolution and discovered I needed to edit /etc/X11/xorg.conf, so I typed sudo vi /etc/X11/xorg.conf and was promptly told that my userid was not in the sudoers file, and that “this incident will be reported.” I’ve just gotten used to OSX and Ubuntu that handle that sort of thing, so this was a bit of an annoyance. Of course, I was able to just su without issue…

So already, I’m not overly impressed. The WTF? moment came when I ran Firefox and discovered that there’s no Flash plugin installed! Who thought that was a good idea? Flash is used on 80%, 90% of all websites, and the Fedora people thought it would be a good idea not to ship the plugin?

OK, so it should be easy to install, right? I clicked on the “click here to download plugin” button and after it chugged for about 10 seconds I was told that it failed. So I went to the Adobe website to download the installer. I’ve now got it installed, but what a joke. The Linux zealots love to claim that “linux is ready for the desktop” but it’s stupid things like this that really show that it isn’t. Granny isn’t going to know how to manually install the Flash plugin.

Objective-C Strings Are Not C Strings

I got bitten today by the fact that Objective-C strings in Cocoa programming are not the same things as plain old C strings. The problem is that Objective-C is essentially an object-oriented veneer on top of plain old C; sometimes it matters that you remember this, and other times it doesn’t. This was one of the times it mattered.

A little background. A while back I wrote and released ExportToArchive, a somewhat useful plugin for iPhoto. It allows you to select photos from your iPhoto library and export them into a few different archive formats. About three days after releasing it, I got an email from a guy who had just tried to export his entire library, 756 photos, into a single archive. He was perplexed because iPhoto just seemed to hang/lock up. The problem was that when I wrote the thing, I never considered that anyone would try to archive more than a few photos at a time. Thus, once you made your selections and started the export, I copied each file to a temporary directory, and then archived the copies. That works great for 10 or 20 photos, but not so well for 756.

The answer to that problem was not to copy the files, but to make a symlink of each photo into that temporary directory and then archive the files by dereferencing the links. It’s still going to take a while to archive the photos, but there will no longer be a copy phase, which should make things faster.

Which leads to today’s adventure. There’s not a native Obj-C or Cocoa method to create a symlink. There is a method on NSFileManager called linkPath:toPath:handler: but that creates a hard link, which won’t span file systems. So I was forced to use the C function symlink which takes two arguments: the source path and the destination path. This seemed easy. I would pass the absolute path name to the original file as the source argument, and the generated name for the link as the second. Easy-peasy.

Well, not really. Since symlink is a C function it, like most other C functions, tells you bugger-all about why a failure occurs. The return value when I tried to call it was -1 which means, “something bad happened.” I then had to consult the C value errno to get more info. The value of errno was 22 which, according to the header file errno.h means:

#define EINVAL 22 /* Invalid argument */

An invalid argument. OK. How about telling me which argument is invalid since I did, after all, pass in two arguments.

I tried escaping spaces and quoting the entire string, but nothing worked. Finally, it dawned on me: Objective-C strings are not C strings. symlink was expecting a plain old C string, but I was passing in something completely different: instances of the Obj-C class, NSString. So, the way to fix this was to pass in C strings, and how do you get C strings from NSString instances? Right, call UTF8string on them. Thus my call to symlink went from

rc = symlink(src, dest);

to

rc = symlink([src UTF8String], [dest UTF8String]);

and all seems to be working properly now.

I’m going to do a bit more testing on this, but I will probably release this new version tomorrow.

Export to Archive iPhoto Plugin

03/13/2009 Update: As I explained here, ExportToArchive does, in fact, work with the new iPhoto ’09.

Ever since I switched to my exquisite Mac Pro, I’ve been using iPhoto. A few weeks ago when Apple announced the new iLife ’08, I bought it. Last week while using iPhoto, I wanted to zip up a bunch of photos so I could move them to another machine. But I couldn’t find anything like this in iPhoto. There’s an exporter, but it doesn’t support zip files. You can right-click on a photo and select “Show File”, but that’s clunky and wouldn’t work well if you had multiple files you wanted to zip up.

So I wrote an exporter plugin to handle this. It’s called Export To Archive, and it supports three archive types: Zip, GZip and BZip2, depending on which flavor you prefer. I personally like bzip2, because the compression is much better than the others, but YMMV.

Once you install the plugin, you select the photos and/or events you want to export, then select Export... from the File menu (if using an older iPhoto, the Export... menu item is under the Share menu). The “Export Photos” dialog opens with a new tab labeled “Archive.” Selecting this tab reveals a pane that looks like this.

The dropdown reveals the other two archive types. Select the type you want and then press the Export button. You will be presented with a “Save As” dialog where you can enter the name of the new archive file. You don’t need to specify an extension as the plugin will append the correct extension based on the archive type. After you enter the name you want and press OK, you should see a progress dialog and then you are taken back to your photo library. If everything went as planned, you have a shiny new archive where you told the plugin to put one. If not, you should get an error message telling you what happened.

The plugin does not keep any directory paths that might have existed in your iPhoto library. I considered making a directory for each “event” (iPhoto 7.x only) but I believe a photo can be in multiple events, so this seemed like a bad idea. Maybe later.

Also, if you select multiple photos with the same name (but from different directories, obviously), then I handle it like Safari does when downloading the same file twice. I append an underscore with a number to the filename before the extension. So if you had two files called 100_1234.jpg, then one would be called 100_1234.jpg and the other would be 100_1234_1.jpg. If there were three files, the second would have _2 appended, etc. This seems like a reasonable solution to this issue.

I should mention one more thing about the compression methods. If you use Zip, then any resource fork info is lost. This probably isn’t a problem, but I felt I should mention it. It’s how the /usr/bin/zip program works. If you choose either GZip or BZip2, then the photos are first put into a tar file and then compressed. The /usr/bin/tar program that comes with OSX does preserve resource fork info into the archive. If you look in the archive, you will see that for every file, there is a similarly-named file that is vastly smaller than the original. If you expand the archive on a Mac, then you only end up with the files you asked for. But on other systems, the resource fork “phantom files” will be expanded to the disk along with the photo files. The file names start with ._ so on Unix systems they will be hidden. On Windows, you will probably see them. Either way, it’s extra crud. I tried to figure out a way to get rid of these files, but there doesn’t seem to be one. And maybe we don’t want to get rid of them, anyway. Thus, if you are going to move your archive to another Mac, you might want to choose GZip or BZip2. If you’re going to it to some other system, choose Zip.

Want it? There are a couple of ways to get it, depending on which version of iPhoto you have.

Both of these installers install the plugin for everyone on the system. PackageMaker.app that Apple provides doesn’t seem to provide a way to install it for just the curent user. This means that the plugin will go to one of two places, depending again on your iPhoto version

  • iPhoto 7.x, 8.x: /Library/Application Support/iPhoto/Plugins
  • iPhoto < 7.x: /Applications/iPhoto.app/Contents/PlugIns

If you’re running iPhoto 7.x or 8.x and you’d rather install it just for yourself, you can download this zip file and install it by hand. You should put it in ~/Library/Application Support/iPhoto/Plugins.

This is a universal binary. I have tested this on a Mac Pro with iPhoto 7.x and 8.x, and on an iBook G4, with iPhoto 5.x. If you have some other config and it doesn’t work for you, let me know, providing a crash dump, if you have one. I don’t know that I can make it work, but I could look into it.

If you’d like the source code, get it here. You’ll need Xcode 2.4.1 to open the project.

That’s about it. This is free software, released under the GNU GPL. If you like it, tell your friends. If there’s a feature you want, let me know, or implement it yourself and provide me a patch to include in the source.

09/12/2007 Update: I’ve now had two three reports of the plugin not working with iPhoto 6.x. That’s the version I was unable to test with, so if you have iPhoto 6, use caution. And if you have iPhoto 6 and it crashes on you, would you send me the crash dump? It can be found in ~/Library/Logs/CrashReporter/iPhoto.crash.log

09/13/2007 Update: I’ve now had several reports that the plugin doesn’t work with iPhoto 6.x, so if you’re using that version, don’t install it. I am trying to figure out how I can test against 6.x, since I don’t have a machine with that version. Stay tuned for updates.

Uninstallation Instructions
If you need to uninstall, delete the ExportToArchive.iPhotoExporter directory. Depending on the version of iPhoto you are using, it will be in one of two places:

iPhoto 7 or 8
/Library/Application Support/iPhoto/Plugins
iPhoto 5 or 6
/Applications/iPhoto.app/Contents/PlugIns

Delete the ExportToArchive.iPhotoExporter directory from whichever place it’s been installed to, and once you restart iPhoto, you should be fine.

iWork vs. CVS

I recently bought the new iWork ’08 suite from Apple and have started using the tools for stuff I used to use NeoOffice for. But I noticed something yesterday that is disconcerting.

Like a lot of people, I store my documents in a CVS repository that is backed up to another disk. I checked in a few documents created with Pages and Numbers, and everything seemed fine. That is, until I re-saved any of the documents.

The problem lies in the fact that a “document” for Pages or Numbers (and probably for Keynote as well) is not a monolithic file like a .doc file from Word. They are directory structures (“bundles” is the Apple term) that the Finder and the applications that use them treat specially. Any program in the /Applications folder is the same type of thing. When you check something into a CVS repository, CVS creates a hidden CVS directory in each sub-directory of the thing being checked in. After checking in one of these documents, I went into the document through Terminal and verified that the CVS directories had been created. So far, so good.

When things went awry was when I re-saved a document. Instead of just changing the necessary files inside the “document,” Numbers deleted and re-created the entire directory structure. Thus, those CVS directories were toast. There’s no good way to recover from this, because those files now look like non-CVS files, but the server already knows about them. To my knowledge, there’s no painless way to handle this situation.

I don’t believe that Subversion is in any better position. When using SVN, you get a .svn directory created in each sub-directory of the document, but those will also get whacked when the document is re-saved. I haven’t tested that assumption, but it seems logical.

I tried to come up with a solution to this, but I’m stumped. I looked into cvswrappers just to see if it could help, but it doesn’t look like it. I also considered a pre-commit script and a post-checkout analog (if there is one), but this didn’t seem like it would really get us there.

The only solution I see is for Apple to stop whacking the directory structure and just change the files inside it that it needs to, and stop molesting the version-control special files. Maybe they can implement this behavior when they add support for OpenOffice and the OpenDocument format…

Capturing Middle-Mouse Click in Safari

06/14/2009 Update: You can download a plugin to do this from here.

I have been a consistent user of the nightly builds of the WebKit project for some time now. For those of you who don’t know, WebKit is essentially the work-in-progress that will be the next production version of Safari. I like the nightlies because they are extremely fast, and while they occasionally have problems, that’s OK.

The point of that discussion was to say that I have stopped using Firefox on my Mac, because the nightly builds of WebKit are so much faster. But there’s one problem. In Firefox I could click a tab with my middle-mouse button, and it would close. Safari doesn’t have that feature, and that’s the one feature from Firefox that I really miss.

So, I’ve been trying to solve this problem using SIMBL. What SIMBL does is let you write a standard Cocoa bundle and have it load into another program, like Safari. Once loaded, you can replace methods in the application with your own versions, in a way known as “method swizzling.” I have successfully written a Cocoa bundle, made the approrpriate changes to make it loadable by SIMBL, and have loaded it into Safari/WebKit. I have logging statements at various points in the bundle, and I can see these on the system console, thus I know it’s loading.

Once the bundle was loading, I needed to pick the objects and their methods that I thought would be most likely to let me do what I needed, and then swizzle in my changes. Using F-Script Anywhere I was able to identify a single tab as an instance of TabButton. Using class-dump I was able to generate header files for Safari that would let me see the methods on TabButton and it’s parents. My first thought was to override mouseUp:. This works, and I can now trap mouse events when you click on any tab. According to the Apple docs, once I get a mouse event, I should be able to call [theEvent buttonNumber] to figure out which button was pressed. Well, maybe. No matter if I clicked with the left- or middle-mouse button, [theEvent buttonNumber] always returned 0. Further digging in the docs turned up an event called NSOtherMouseUp that is sent when a button “other” than left- or right-mouse is clicked. Supposedly, I should be able to override otherMouseUp: to get those events. I have successfully swizzled this method, but it never gets called. I know that TabButton had a version of this method, because when I swizzle, I can tell if there was already a method there, and there was. I’m just not sure why it isn’t being called.

So what did I get working? Well, after working a long time trying to get the middle-mouse detection working, I decided to punt for the moment. I added some code to the mouseUp method to check the event for modifiers and if the user held down Command while clicking, then I will close the tab. This is close to what I want, but nearly as sexy as just middle-mouse clicking. In case you’re interested, my TabButton.m looks like this:

 	 1 #import "TabButton.h" 	
         2 #import "WebKit/WebKit.h" 	
         3  	
         4 @implementation TabButton (MCCSwizzle) 	
         5 - (void)_mcc_mouseUp:(NSEvent *) theEvent 	
         6 { 	
         7   NSLog(@"_mcc_mouseDown"); 	
         8   int buttonNumber = [theEvent buttonNumber]; 	
         9  	
        10   NSLog(@"buttonNumber: %d", buttonNumber);
        11   NSLog(@"type: %d", [theEvent type]); 	
        12   NSLog(@"modifierFlags: %d", [theEvent modifierFlags]); 	
        13  	
        14   if ([theEvent modifierFlags] & NSCommandKeyMask) { 	
        15     NSLog(@"Cmd-Click!"); 	
        16     [self closeTab: theEvent];
        17   } else { 	
        18     [self _safari_mouseUp: theEvent]; 	
        19   } 	
        20 } 	
        21 @end

and then the swizzling looks like this:

 	 1 #import  	
         2 #import "AppController.h" 	
         3 #import "MiddleClickClose.h" 	
         4  	
         5 typedef struct objc_method *Method; 	
         6  	
         7 struct objc_method { 	
         8   SEL method_name; 	
         9   char *method_types; 	
        10   IMP method_imp; 	
        11 };
        12  	
        13 BOOL MCCRenameSelector(Class _class, SEL _oldSelector, SEL _newSelector) 	
        14 { 	
        15   NSLog(@"OLD: %s", _oldSelector); 	
        16   NSLog(@"NEW: %s", _newSelector);
        17 
        18   Method method = nil; 	
        19  	
        20   // Look for the methods
        21   method = (Method)class_getInstanceMethod(_class, _oldSelector);
        22   if (method == nil)
        23     return NO;
        24 
        25   // Point the method to a new function 	
        26   method->method_name = _newSelector;
        27   return YES; 	
        28 } 	
        29 
        30 @implementation MiddleClickClose
        31 + (void) load 	
        32 { 	
        33   int rc;
        34 
        35   rc = MCCRenameSelector([TabButton class], @selector(mouseUp:), 	
        36                                    @selector (_safari_mouseUp:));
        37   NSLog(@"RC: %d", rc);
        38 
        39   rc = MCCRenameSelector([TabButton class], @selector(_mcc_mouseUp:),
        40                                                  @selector(mouseUp:)); 	
        41   NSLog(@"RC: %d", rc);
        42 
        43   rc = MCCRenameSelector([TabButton class], @selector(rightMouseUp:),
        44                                    @selector (_safari_rightMouseUp:));
        45   NSLog(@"RC: %d", rc);
        46 
        47   rc = MCCRenameSelector([TabButton class], @selector(_mcc_rightMouseUp:), 	
        48                                                  @selector(rightMouseUp:)); 	
        49   NSLog(@"RC: %d", rc);
        50 
        51   NSLog(@"MiddleClickClose loaded");
        52 }
        53 
        54 + (MiddleClickClose*) sharedInstance
        55 {
        56   static MiddleClickClose* plugin = nil;
        57 
        58   if (plugin == nil)
        59   {
        60     plugin = [[MiddleClickClose alloc] init];
        61   }
        62 
        63   return plugin;
        64 } 	
        65 @end

Line 35 renames Safari’s original mouseUp: method to _safari_mouseUp: and then line 39 renames my _mcc_mouseUp: method to mouseUp:. The otherMouseUp: method is handled on lines 43 and 47, but as I said otherMouseUp never gets called.

What’s especially frustrating about this is that I know Safari knows how to bag a middle-click, because I frequently will middle-click on a link in a web page, and Safari will open that link in a new tab. So, why can’t I get a middle-click in the TabButton instance? Does anyone have any ideas on this? I’d appreciate any pointers. I feel like I’m thiiiiiiiiiiis close to getting this working, but there’s some small piece of info that is eluding me.

03/03/2008 Update: I got this working and have released it under the GPL. Read about it and get it over here.

Third Time’s The Charm on Mac RAM — I Hope

Being at the end of a UPS route is hard. Tuesday morning I saw from the UPS website that my third set of RAM from Crucial was “out for delivery” from the local hub. What this means is that it’s on a truck, heading for my house. Unfortunately, we’re at the tail-end of said route, and I have yet to receive a UPS delivery before 4:00 PM. We were going to be leaving around 5:00 and since UPS is a “drop and run” courier, if it got there after we left, it would have been sitting on the porch for several hours until we got home. Fortunately, it arrived about 4:45. So it got inside the house, but wouldn’t get inside the Mac until later.

When I got home Tuesday night, I installed the RAM. So far, it’s working perfectly. Of course, the first set worked perfectly for a week or so, so I’ll have to just keep an eye on it. Thus, once again, my Activity Monitor looks like this

Mac Pro RAM Woes

You may remember how excited I was about a month ago when I got the 2GB RAM upgrade from Crucial for my Mac Pro. Well, about two weeks ago I happened to notice in Activity Monitor that I only had 3GB of RAM. That wasn’t right; I had 4GB. I started testing, and discovered a problem with the RAM. System Profiler was reporting 6 512MB sticks of RAM, instead of 4 512MB and 2 1GB sticks. I tried moving the sticks around to various banks, but never got it to see more than 3GB. Also, there are four LED’s on each of the risers that correspond to the four RAM banks. When you boot the system, these lights come on for about 2 seconds, then go out. There was one Crucial stick whose LED stayed on. No matter which bank I put that stick in, its light stayed on.

Saddened by this, I called Crucial. I spent just a few minutes on the phone with them before they agreed that there was clearly a problem, and that they would replace them. They have excellent customer and technical support. I shipped the two sticks back to them and waited.

Two days ago UPS arrived with my replacement sticks. I shut down my Mac and put the two new sticks in banks 3 and 4 of riser A, leaving the 512MB sticks in banks 1 and 2 of risers A and B, and booted. I logged-in, brought up Activity Monitor, only to see 3GB reported. System Profiler says 6 512MB sticks, just like before. The new RAM is doing exactly what the first set of RAM did. I tried every combination of placement of the sticks I could think of, including removing ALL the Apple RAM, and just putting the Crucial sticks into banks 1 and 2 of riser A. In that case, the Mac only saw 1G of RAM.

So it was back to Crucial tech support. They agreed it sounded like a bad stick (again) and are going to replace them again. I just shipped back the two latest sticks, and am once again waiting on a shipment from Crucial. I hope this one works. If it doesn’t, I’m just going to pony up the 2x extra and buy direct from Apple.

The Crucial guy said it was “hard to believe” that I got two bad sticks in a row, and I agree. But what else could it be? If I only had two banks for RAM in the computer, then you could blame it on the computer itself. But I’ve got eight banks to play with, and have tried them all. I don’t think it’s a problem with the Mac itself. I could be wrong, but I don’t think so.

AppleScript and iTerm: Sweet!

I use iTerm for all my command-line needs. I really like it, especially the tabbed interface. I typically have two to three terminals open for my local box, plus two to three for various systems in the company rack. iTerm has a nice bookmark feature that lets you save commands (like ssh me@foo) to open these various windows, but it’s not exactly what I want. The reason for this is that I either have to leave the bookmark drawer open all the time, or I have to click to open it, then double-click the bookmark I want to launch, then close the bookmark drawer. And that’s a real drag for me, because I’m not really a mouse guy.

So yesterday I started thinking that it had to be possible to do this using AppleScript, and indeed it is. My solution is not optimal, as I had to use two files, but it’s close to optimal. It’s approaching optimal. Here’s the AppleScript file, which is called it.

on run argv 		
    tell application "iTerm" 		
            activate 		
 		
            tell the first terminal 		
                    launch session (item 1 of argv) 		
            end tell 		
    end tell 		
end run

Next is a regular shell script, called it that calls it.scpt:

#!/bin/bash 		
  		
if [[ $# == 0 ]] 		
then     		
    echo "Usage: it " 		
    exit 	
fi 		
  		
osascript ~/bin/it.scpt $*

You can see that the shell script checks to see if you’ve specified a bookmark name to launch. If you haven’t, it tells you how to run it. Once it’s established that you have specified a name, it calls the AppleScript file it.scpt, which I’ve placed in ~/bin for convenience. (Both files are in ~/bin on my system.) The AppleScript tells the currently-running iTerm to activate (probably not needed) and then tells it to launch the requested bookmark in a new tab. I don’t need to worry about the case where iTerm isn’t running, because I would execute this script from within iTerm. If you specify a non-existent bookmark, it just opens a new shell on your local system, which is OK, I guess.

So, to run it, I would type something like

it web1

to open the bookmark called “web1.” And that’s what I wanted.

I was a bit surprised that I was unable to just have one file. I should have been able to put a she-bang line in it.scpt and have it work. In other words, I should have been able to have

#!/usr/bin/osascript 	

and then the rest of the script, but I got an error when I tried that.

If anyone knows an easier way to do this, please let me know.

04/11/2007 14:20 Update: A reader sent in how you can combine the two scripts into one, using osascript‘s -e switch. I knew about this switch, which let’s you specify the program on the command line, but I’ve seen so many horrible (ab)uses of the same option in Perl that I didn’t even try it. What I didn’t know was that you can have embedded line feeds inside the quotes, so you can still have a nicely formatted script. Here’s the new and improved, single-file version of it:

#!/bin/bash 		
  		
 if [[ $# == 0 ]] ; then 		
     echo "Usage: $0 " >&2 		
     exit 1 		
 fi 		
  		
 osascript -e 'on run argv 		
     tell application "iTerm" 		
             activate 		
  		
             tell the first terminal 		
                      launch session (item 1 of argv) 		
             end tell 		
     end tell 		
 end run' $@

04/12/2007 11:46 Update: This tip got posted over at MacOSXHints.com and has gotten some comments. Based on those comments, below is the latest version, which includes the ability to get a list of available bookmarks to launch by typing it list. Here it is:

#!/bin/bash 		
  		
 if [[ "$#" = "0" ]]; then 		
     echo "Usage: 'it bookmarkname' or 'it list'" && exit 1 		
 elif [[ "$1" = "list" ]]; then 		
     defaults read ~/Library/Preferences/iTerm|grep Name |grep -v NSColorPicker|awk '{$1="";$2=""; print $0}'|tr -d ';' 		
 else  		
 osascript <<ENDSCRIPT 		
 on run argv 		
   tell application "iTerm" 		
     activate 		
     tell the current terminal 		
       launch session "$1" 		
     end tell 		
   end tell 		
 end run 		
 ENDSCRIPT 		
 fi