Stairways Software Icon

Stairways Software

Excellence In Software For Over Ten Years – Only with a Mac

Stairways Software Technical Blog (Full Archive, RSS Feed)

Uninstalling Adobe Acrobat DC

I foolishly installed Adobe Acrobat to test something and then discovered there is no uninstaller. Out with the hammer…

/Library/Receipts/InstallHistory.plist lists the following packages:

  • com.adobe.acrobat.DC.reader.app.pkg.MUI
  • com.adobe.acrobat.DC.reader.browser.pkg.MUI
  • com.adobe.acrobat.DC.reader.appsupport.pkg.MUI
  • com.adobe.RdrServicesUpdater
  • com.adobe.armdc.app.pkg

I imagine there is some list of files somewhere, but I don't know where, but I found the following files in need of deleting:

  • /Applications/Adobe Acrobat Reader DC.app
  • /Library/Internet Plug-Ins/AdobePDFViewer*
  • /Library/LaunchDaemons/com.adobe.ARMDC.*
  • /Library/LaunchAgents/com.adobe.ARMDC.Communicator.plist
  • /Library/PrivilegedHelperTools/com.adobe.ARMDC.*
  • /Library/Application Support/Adobe

That last folder might have some other Adobe stuff you want - it didn't have anything I wanted.

I have no Adobe software on my Mac, so there wasn't much I could break - if you have other Adobe software you want to keep running, then be "Careful with That Axe, Eugene".

Posted Wednesday, June 3, 2015. Permalink. Post a Comment.

Animated Controls

Yosemite has fancy new animated controls for basic things like check boxes and radio buttons.

I would hope there was an API to make this work for custom controls, but I have yet to find any.

So I have posted some code which demonstrates a simple way of doing an animated toggle button.

I don't claim this is the only, or even the best, way to do this. But it is relatively simple, and aside from the drawing code, it is resuable.

Posted Tuesday, July 29, 2014. Permalink. Post a Comment.

NSAppleScript is Really Not Thread Safe

Jeff Johnson recently posted an article on NSNotificationCenter not really being thread safe, and in a similar vein, this article is to warn you that NSAppleScript is often not safe even on the main thread if you have other things happening in your application.

It's well known that NSAppleScript is not thread safe, but in fact its mostly not safe at all even on the main thread is probably not as well known as it should be. The problem is that [NSAppleScript executeAndReturnError] appears to be a blocking call, and that calling it on the main thread should be safe enough as long as you're careful to ensure the AppleScript is going to finish quickly.

Unfortunately, the problem is that its not really blocking - sure, this part of your main thread stops, but other things can run on your main thread while you're blocked here, which can easily violate a whole lot of assumptions.

For example, say you are on the main thread, and you start a "transaction", make the first part of a change, call [NSAppleScript executeAndReturnError], and then finish the change and end the "transaction". It appears safe, but [NSAppleScript executeAndReturnError] allows other main thread activity to happen such as timers and event processing, and so if you're doing all your transactions "safely" on the main thread, suddenly all those main thread elements have to deal with the potential of a transaction in progress. Yuck!

Here is a simple example:

- (void) logit;
{
 NSLog( @"performSelector:afterDelay: ran" );
}

- (void) test;
{
 NSLog( @"Start Test" );
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  NSLog( @"dispatch_after ran" );
 });
 [self performSelector:@selector(logit) withObject:nil afterDelay:0.1];
 (void)[[[NSAppleScript alloc] initWithSource:@"delay 1"] executeAndReturnError:NULL];
 NSLog( @"Finish Test" );
}

All the code runs on the main thread, so you would expect the Start Test and Finish Test to appear first, and then the logit method and the dispatch_after block to run. But in fact both of those are executed before the Finish Test.

So basically the only safe way to use [NSAppleScript executeAndReturnError] (and probably most of anything else in NSAppleScript) is in a block on its own on the main thread. Essentially:

dispatch_async(dispatch_get_main_queue(), ^{
 (void)[[[NSAppleScript alloc] initWithSource:@"delay 1"] executeAndReturnError:NULL];
});

Otherwise, if you're doing anything before or after the NSAppleScript calls, you have to assume lots of other parts of your code could run in between which is almost impossible to ensure correctness.

What an ugly mess!

Posted Thursday, April 24, 2014. Permalink. Post a Comment.

Adding Basic Accessibility to a Custom View

One of the things I did for Keyboard Maestro 6.4 was to improve accessibility support. There were several parts to this, but primarily it revolved around either just adding an accessibility title to any image-only buttons, or adding accessibility support to custom views.

Adding an accessibility label can be done easily in Interface Builder, and developers should simply remember to set it anytime the view doesn't have a normal title that can be read by VoiceOver. So if you have an image button, add an accessibility label.

The more challenging one is with custom views, but it simply requires responding to a set of accessibility methods. I found the documentation from Apple to be rather opaque, but eventually muddled through to get a good result (with the help of a couple diligent blind users who gave me a lot of assistance in understanding what was and wasn't working and what needed to be done.

In the case of a custom NSControl, the work needs to be done in the cell. And because Objective C does not have mix-in objects, it's a bit difficult to design a system that doesn't require a bunch of boilerplate code for each custom view. But this sample code does not attempt to solve that issue, just to answer the basic questions of what should you return from where. It is not a framework suitable for directly including in any project.

The only files of interest at the CustomeView and CustomCell files, the rest is just a basic Xcode template, with a custom view added to MainMenu.xib.

Download Sample Code

And please note, I'm new to this and by far not an expert. This appears to work, and passes the requirements of my users, but whether it is the best way of doing it, or there is more required, or less required, I don't pretend to know for sure. Hopefully it's useful.

If someone wants to take the project and put it up on github or some such place where the code is more searchable, that would be fine (let me know and I'll add a link).

Posted Wednesday, March 26, 2014. Permalink. Post a Comment.

NSDistributedLock lockDate docs are dangerously wrong

NSDistributedLock documentation claims lockDate returns nil if the lock does not exist, but in fact it returns a date of 2001-01-01 00:00:00 +0000. This simple code can test it:

NSDistributedLock* dlock  = [[[NSDistributedLock alloc] initWithPath:@"/tmp/peter-34593459835.lock"] autorelease];
NSLog( @"NSDistributedLock is %@ and lock date is %@", dlock, [dlock lockDate] );

To understand why this is horrendously bad, a reasonable implementation of locking will check lockDate, and if it is old, break the lock with breakLock, either before or after failing to try the lock. This means you will call breakLock when no lock file exists at the time of your call to lockDate, which leaves a humongous race condition hole in your code - not much better than no locking at all.

Consider this code case:

while ( ![lock tryLock] ) {
  NSDate* locked = [lock lockDate];
  if ( locked && [locked timeIntervalSinceNow] < -60 ) [lock breakLock];
  usleep( 1000 );
}

If another process has the lock at line 1 and then releases it for line 2, and then acquires it for line 3, you'll assume the lock is very old and break it. This is disastrous and I can see no safe workaround for this terrible behaviour.

Luckily, writing your own version of NSDistributedLock is relatively straight forward.

So if you use NSDistributedLock in your code, verify the behaviour of lockDate, and if you get the same result, look very closely at your lock breaking code to see what race conditions have been left in there by this poor behaviour.

Posted Friday, June 22, 2012. Permalink. 1 Comments.

Finder AppleScript "the selection" speed.

Keyboard Maestro includes a For Each action category to loop over the Finder’s selection. Usually the performance was OK, but occasionally I'd see horrendous performance. I tracked it down to the speed of this simply AppleScript:

tell app "Finder" to get the selection

In some cases it would take seconds or even tens of seconds per item returned (this seemed particularly bad in my Trash folder which has thousands of items - selecting even a dozen files could mean the AppleScript took a minute to run!

I comments on Twitter, and Takaaki Naganoya responded on Twitter to try "selection as alias list".

Boy was he right. The response to this:

tell app "Finder" to get the selection as alias list

is near instantaneous, every time. You get a slightly different result format for the results, but in all likelihood you'll get functionally the same behaviour at vastly improved speeds.

These results were on my Mac running 10.7.4 so it may be wise to verify the issue across a wider range, but since I cannot see any downside, this is the code I'll use in Keyboard Maestro in the future.

Posted Friday, June 22, 2012. Permalink. Post a Comment.

Remembering the path with tcsh in Terminal/Lion

In Lion, the new version of Terminal remembers your Windows and Tabs, including your scroll history, and with bash, also your path. If you use tcsh (as I have for 20 years or so), then it does not remember your path - here is how to fix it.

A little digging finds that bash sets this up in /etc/bashrc, specifically with update_terminal_cwd which it adds to PROMPT_COMMAND. It is careful to only do this for Apple_Terminal and while not inside Emacs. Since I don't use any other terminals (except TouchTerm on my iPhone) and I don't use Emacs, I've ignored that, but keep in mind there may be cases you need to disable this.

The trick is done by outputting a URL in an escape sequence.

In tcsh, you can set an alias to a command that is run immediately before printing your path, so you can set it to print the magic escape sequence using something like this:

alias precmd ~/perl/update_terminal_cwd.pl

update_terminal_cwd.pl duplicates the behaviour of update_terminal_cwd in /etc/bashrc:

#!/usr/bin/perl

my $pwd = $ENV{PWD};
$pwd =~ s! !%20!g;
$pwd = "file://$ENV{HOSTNAME}$pwd";
printf( "\e]7;%s\a", $pwd );

Add the alias to you .login and Terminal will remember your paths in tcsh too.

Posted Sunday, August 7, 2011. Permalink. Post a Comment.

Field Editor Issues

The editing a text field, cocoa uses a shared field editor, which switches text fields as the user tabs around the window. This technique is designed to save resources, and it is unlikely it is needed on modern hardware, but for now we're stuck with it.

The field editor brings up a number of issues, here are some helpful tips for handling them.

There is usually a single shared editor for a window, but the window delegate can provide a custom field editor or different editors for different text fields. To return a custom field editor, use the window delegate method windowWillReturnFieldEditor:toObject:

To test whether a text field is being edited, check [field currentEditor], which returns the field editor or nil if the text field is not being edited.

To get from the field editor to the current text field, you use (NSTextField*)[fieldEditor delegate].

The field editor is added as a subview of the text field. So to determine if a text field is the current first responder, use [textField.window.firstResponder isDescendantOf:textField]. Note that you need to check that textField.window.firstResponder isKindOfClass:[NSView class] first.

Posted Friday, May 6, 2011. Permalink. Post a Comment.

Installing ImageMagick

I‘ve beat my head against installing ImageMagick far too many times. Its a powerful tool very useful for scripted image manipulation, but it has always been a royal pain to install.

The following describes how to install it under Mac OS X 10.6 (Snow Leopard) in 64 bit architecture (if applicable). All of the files are downloaded into ~/unix/install and all the components are installed in ~/unix/local, except for the perl modules, and (annoyingly) some ImageMagick man pages which refuse to honour the prefix, though no doubt there is some other magick (ha!) incantation that would force the man pages to the right location.

Although the code below is written like a script, you can‘t (well, certainly shouldn‘t) just run it as a script. For one thing, by the time I‘ve finished writing this article, one or other of the components will likely have been updated. For another, you really should check that each part compiles and installs before proceeding. Its a slow and painful process, but a lot faster with a plan to follow.

Good luck!

mkdir ~/unix
mkdir ~/unix/install
cd ~/unix/install
setenv MACOSX_DEPLOYMENT_TARGET 10.6
setenv LDFLAGS -L$HOME/unix/local/lib

curl -O ftp://ftp.remotesensing.org/pub/libtiff/tiff-3.9.1.tar.gz
tar zxf tiff-3.9.1.tar.gz
cd tiff-3.9.1
./configure --enable-shared --prefix=$HOME/unix/local
make
make check
make install
cd ..

curl -O ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng-1.2.40.tar.gz
tar zxf libpng-1.2.40.tar.gz
cd libpng-1.2.40
./configure --enable-shared --prefix=$HOME/unix/local
make
make install
make check
cd ..


curl -O http://www.ijg.org/files/jpegsrc.v7.tar.gz
tar zxf jpegsrc.v7.tar.gz
cd jpeg-7/
./configure --enable-shared --prefix=$HOME/unix/local
make
make check
make install
cd ..

curl -O http://mirror.dknss.com/nongnu/freetype/freetype-2.3.9.tar.gz
tar zxf freetype-2.3.9.tar.gz
cd freetype-2.3.9
./configure --enable-shared --prefix=$HOME/unix/local
make
make install
cd ..

curl -O http://internode.dl.sourceforge.net/project/ghostscript/GPL%20Ghostscript/8.70/ghostscript-8.70.tar.gz
tar zxf ghostscript-8.70.tar.gz
cd ghostscript-8.70
./configure --disable-cups --prefix=$HOME/unix/local
make
make install
cd ..

curl -O http://www.vg.kernel.org/pub/mirrors/gentoo/source/distfiles/pkg-config-0.23.tar.gz
tar zxf pkg-config-0.23.tar.gz
cd pkg-config-0.23
./configure --enable-shared --prefix=$HOME/unix/local CPPFLAGS=-I$HOME/unix/local/include LDFLAGS=-L$HOME/unix/local/lib
make
sudo make install
cd ..

curl -O ftp://ftp.gnu.org/pub/gnu/gettext/gettext-0.17.tar.gz
tar zxf gettext-0.17.tar.gz
cd gettext-0.17
./configure --enable-shared --prefix=$HOME/unix/local CPPFLAGS=-I$HOME/unix/local/include LDFLAGS=-L$HOME/unix/local/lib
make
make install
cd ..

curl -O http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.9.2.tar.gz
tar zxf libiconv-1.9.2.tar.gz
cd libiconv-1.9.2
./configure --enable-shared --prefix=$HOME/unix/local CPPFLAGS=-I$HOME/unix/local/include LDFLAGS=-L$HOME/unix/local/lib
make
make install
cd ..

curl -O http://laotzu.acc.umu.se/pub/gnome/sources/glib/2.20/glib-2.20.5.tar.gz
tar zxf glib-2.20.5.tar.gz
cd glib-2.20.5
./configure --enable-shared --prefix=$HOME/unix/local CPPFLAGS=-I$HOME/unix/local/include LDFLAGS=-L$HOME/unix/local/lib --with-libiconv
make
make install
cd ..

curl -O  http://www.imagemagick.org/download/delegates/liblqr-1-0.1.0-1.tar.gz
tar zxf liblqr-1-0.1.0-1.tar.gz
cd liblqr-1-0.1.0
./configure --enable-shared --prefix=$HOME/unix/local GLIB_CFLAGS=-I$HOME/unix/local/include GLIB_LIBS=-L$HOME/unix/local/lib
make
make install
cd ..

[sudo is needed to install some man pages and the perl modules]

curl -O  ftp://mirror.aarnet.edu.au/pub/ImageMagick/ImageMagick-6.5.7-6.tar.gz
tar zxf ImageMagick-6.5.7-6.tar.gz
cd ImageMagick-6.5.7-6
./configure --enable-shared --prefix=$HOME/unix/local CPPFLAGS=-I$HOME/unix/local/include LDFLAGS=-L$HOME/unix/local/lib --with-perl
make
sudo make install
cd ..

Posted Tuesday, November 10, 2009. Permalink. Post a Comment.

Open Finder Selection in Specific Application

Recently on the Keyboard Maestro User Group someone asked how to open the finder selection in a specific application. Keyboard Maestro does not have a direct action for this, but you can easily add an Execute AppleScript text action to do this. Here is a simple script to do this.

tell application "Finder" to set finderfiles to selection as list
set finderaliases to {}
repeat with i in finderfiles
    set a to i as alias
    set finderaliases to finderaliases & a
end repeat
tell application "BBEdit"
    activate
    open finderaliases
end tell

Posted Sunday, July 12, 2009. Permalink. Post a Comment.

Creating an NSImage from an NSView

There are many different techniques for creating an NSImage from an NSView - finally I found one that works well!

There are various techniques:

  • lockFocus on the image, and then drawRect: the view

But this only draws the root view, not subviews.

  • lockFocus on the image, and then drawRect: the view and draw subviews

But its nigh on impossible to actually get the transformations correct for adjusting to the subview co-ordinate systems.

  • lockFocus on the view, and use initWithFocusedViewRect:

But this only gets the visible parts of the view.

  • use dataWithPDFInsideRect

But this is quite slow and has some drawing artefacts when the resulting image is displayed on the screen.

I finally found Gerd Knops’ post which yields a good result.

- (NSImage *)imageWithSubviews
{
 NSSize mySize = self.bounds.size;
 NSSize imgSize = NSMakeSize( mySize.width, mySize.height );
 
 NSBitmapImageRep *bir = [self bitmapImageRepForCachingDisplayInRect:[self bounds]];
 [bir setSize:imgSize];
 [self cacheDisplayInRect:[self bounds] toBitmapImageRep:bir];
 
 NSImage* image = [[[NSImage alloc]initWithSize:imgSize] autorelease];
 [image addRepresentation:bir];
 return image;
}

I am combining this with Matt Gemmell’s MGViewAnimation with good results for use in alpha fading views under 10.5.

Posted Tuesday, April 21, 2009. Permalink. 1 Comments.

Technical Blog

I get a lot of value out of some of the technical blogs I read, such Mike Ash’s NSBlog and Matt Gallagher’s Cocoa with Love, as well as from sites like CocoaDev.

It is always good to return the favor and put out some useful information and in the past I have put information up on my personal web site, but I thought it was about time to create a proper developer oriented blog as a place to post technical information which is not directly related to our customers but which is related to what we are doing as a company.

Hence this blog.

I hope to post useful information related to whatever I am doing, especially related to anything that proves less that obvious on the first attempt and resists a google search to easily find the answers. Most of it will likely be related to Mac or iPhone programming, but occasionally it may stray in to other technical areas.

Posted Sunday, March 29, 2009. Permalink. Post a Comment.

Syndicated Content for Technical Blog

Other Blogs

Buy Now

User Database

Stairways