TweetFollow Us on Twitter

Aug 01 Application Development

Volume Number: 17 (2001)
Issue Number: 08
Column Tag: Application Development

DropScript

by Wilfredo Sanchez

Or How to Build a Way Easy Yet Spiffy App

Quick, I Need A Demo

Last year at Apple's Worldwide Developers Conference I was giving a talk on BSD in Mac OS X. Basically, I was standing in front of a (full) room of Macintosh developer explaining why Unix is good for Mac OS.

There has been a fair amount of debate over the value of keeping the BSD command line (Terminal.app) and the BSD toolset in the base operating system. The majority of Mac OS users will not need to interact with the command line, and therefore the Terminal will not be of great use to them. (There is also the argument that the very presence of the command line option in the system will give developers an undesireable crutch; that users might end up having to use the command line.)

I actually agree that Terminal.app is not a necessity in the base system, though I'm glad it's there for my own convenience. (It is a necessity in the developer toolset.) I also agree that requiring users to deal with the decidedly arcane Unix interface would be a grave mistake. However, even with no way to get to an interactive command line, the presence of the BSD toolset in the standard installation of Mac OS is of great value to developers and therefore to users. My argument is simple: the BSD commands provide another valuable API to developers; they enable software authors to do great things will less effort.

In order to make my case that the BSD toolset is important, it helps to have a good demo for the talk, in true WWDC fashion. Certainly I can't let the QuickTime folks get all of the applause. BSD provides many small programs (often called commands, as they tend to be invoked by users on an interactive command line), each of which performs some specialize function. A command called cp, for example, can copy a file; another, mv, can rename a file. An application author can write programs that invoke BSD commands to do certain task. These commands can be shell scripts which in turn may invoke several other commands in sequence. I figured that an application that did this would be a good demo.

I had heard of an application for NeXTStep (can't remember the name; I've never seen it), which would let you graphically create a shell script by chaining commands together. That sounded hard to write in a week... I've seen people use MacPerl, which lets you create droplets, applications onto which you can drop a file and have a perl script process the file for you. Now wouldn't it be cool if you could take any BSD command and turn it into a drop application? That didn't sound so hard, so I started thinking about it.

Designing A Surprisingly Simple Application

What I needed was an program that could turn a command line tool into an application. Many command-like tools operate on files. You invoke the program by name and give it file names as arguments. For those of you not familiar with the command line, the basic usage is fairly simple. Say I have a program called gzip, which will compress a file. I want to compress the file Big File, so I type the following into the terminal:

gzip "Big File" 

and I end up with a smaller file called Big File.gz (the .gz file name extension denotes a gzip-compressed file). Now I want to be able to do the same thing in Finder by dragging a file onto an icon rather than using the command line.

My droplet-generating program therefore needs to create a new application which invokes the desired command (typically a shell script)1 for me, with my file as an argument. I started by writing a droplet application which I would use as a template, thinking that the droplet generator would copy the template application and replace the command it invokes with another. I wanted to avoid the need to compile a new binary, since many users will not have the developer tools installed; while it's probably OK to say only developers can create droplets, I'd rather not require the developer tools if I don't need to.

Writing the primordial droplet

My plan was to create an application which would contain a shell script in it. Whenever the application is asked to open a file, it will invoke the script with the file's path as an argument. The generator would therefore only need to copy the original app and replace the script with a new one.

I decided to write the application using Cocoa. Mostly, this is because Cocoa is the only application toolkit I'm proficient at (I don't write many apps), but the reason Cocoa is the only toolkit I know is that Cocoa is an excellent toolkit. Cocoa does an excellent job of minimizing the amount of work I need to do. For example, when a user drops a file onto an application with Finder, what happens is that Finder launches the application and sends it an AppleEvent telling it to open the file. Any decent Mac OS application which opens files has a File menu with an Open menu item which also opens a file. Cocoa automatically provides menu items for quitting, hiding the app, hiding the other apps, as well as the Edit functionality of copy and paste.

I start by creating a new Cocoa Application project in Project Builder. In the resources group of the project, I add an new empty file called "script" which will contain my shell script. We'll start with a simple script:

#!/bin/sh
gzip -9 "$@"

This script will compress files with gzip using the maximum compression level.

Most Cocoa applications instantiate a controller object. This object reacts to user actions and drives the application. Very often the controller is "owned" by the main user interface; I'll do that for this app, but more on that later. First, I'll consider what the controller needs to do.

For this app, I already know that it needs to be able to open a file, so I'll define a method called open:. open: is what is called an action; actions are methods which are called by other objects in the AppKit to trigger some activity in the application. AppKit interface elements can be set up to invoke an action on another object, called its target. For example, pressing a button or sliding a slider will cause the button or slider to send an action to its target. Action methods always take one argument: the id of the object which sent the action message to the target. The interface for our controller object (which I'll call a DropController) follows:

#import <Foundation/NSObject.h>

@class NSString;

@interface DropController : NSObject

/* Instance variables */
{}

/* Actions */
- (IBAction) open: (id) aSender;

@end

Next, I open up the MainMenu.nib file in the project, which opens in Interface Builder. I then import the header DropController.h (click on the Classes tab, and use the Classes->Read Files... menu item), and instantiate that object (Classes->Instantiate) in the nib file. When the application is launched, the MenMenu.nib interface (which is the main application interface) is loaded automatically. It will in turn instantiate the DropController object. I want the File->Open menu item to invoke the open: action method, so I simply connect the menu item to the controller and set the target method to open:. (You'll be wanting to go through an Interface Builder tutorial if I've lost you; If you want to take my word for it, this is very easy.) And because I won't be needing them, I can delete all of the menu items other than the Apple menu and the File->Open item.

If you are new to Interface Builder, that was the hard part. The rest is a breeze from here.

I need to locate the path to the shell script (or command) I've put into the application (it'll be in the Resources folder within the app), so I'm going to keep that information in an instance variable of the controller. I therefore edit DropController.h to add an instance variable:

/* Instance variables */
{
@private
  NSString* myScriptFileName; //we'll keep the path to the script
}

Side note: I make a habit of always marking my instance variables as private, as allowing other objects to access them directly is almost always a mistake. Subclasses cannot directly modify a private instance variable, instead, they must use the provided API, a core tenet of data encapsulation.

We can begin writing the implementation of the controller (DropController.m) now:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "DropController.h"

@implementation DropController

/* Inits */

- (id) init
{
  if ((self = [super init]))
    {
      myScriptFileName =
        [[[NSBundle mainBundle] pathForResource:@"script" ofType:nil] retain];
    }
  return self;
}

- (void) dealloc
{
  [myScriptFileName release];

  [super dealloc];
}

/* Actions */

@end

The init method initializes the instance variables, in this case setting myScriptFileName to the path to the script and retaining the string (marking it so that it isn't deallocated until we are done with it). When the controller is discarded (the app quits), the dealloc method releases the string (expresses the controller's sudden disinterest in the string's safety). Andrew Stone discussed the use and power of NSBundle in his article "Dynamic Bundles and Runtime Loading in OS X" several issues back.

And then I tell the controller how to open a file by implementing the open: action:

/* Actions */

- (void) runScriptWithFiles: (NSArray* ) aFileList
{
  [NSTask launchedTaskWithLaunchPath: myScriptFileName
                           arguments: aFileList];
}

- (IBAction) open: (id) aSender
{
  NSOpenPanel* anOpenPanel = [NSOpenPanel openPanel];

  if ([anOpenPanel runModalForTypes:nil] == NSOKButton)
    {
      [self runScriptWithFiles: [anOpenPanel filenames]];
    }
}

The open: method creates an open panel and runs it modally, then runs the script on the selected files. I put the actual code to run the script in its own method because I'll be needing it again later.

At this point, I have an application which I can launch, then bring up the File->Open menu, and have it run a script on a file (or a few files) for me. But I still need to handle AppleEvents from the Finder or other sources. AppKit makes this easy. The AppKit application object (which I am using already, though I've not had to know it yet) handles AppleEvents for me. All I need it to ask it to tell me when it gets an open message.

First, I go back to the MainMenu.nib in Interface Builder. We need to mark the controller object as the delegate of the application object. A delegate is an object to which another object sends specific methods when something interesting happens. In Interface Builder, we connect the File's Owner object (which, for MainMenu.nib is the application object) to the controller object as the delegate.

If the application's delegate implements the method application:openFile:, the application will automatically call that method whenever the application is asked to open a file, such as with an AppleEvent. (This and other delegate methods are documented in the NSApplication docs.) All I have to do, then, is implement the method:

/* Application delegate */

- (BOOL) application: (NSApplication*) anApplication
    openFile: (NSString*) aFileName
{
  [self runScriptWithFiles: [NSArray arrayWithObject: aFileName]];

  return YES;
}

Now dropping files onto the droplet's icon works! With that, I've finished up the prototype droplet, which can compress files.

Writing the droplet generator

The droplet generator should itself be an application, so that I do not need access to the terminal. In fact, I'd like to be able to write a shell script in Text Edit, and then drop that file onto my droplet generator. That is, the droplet generator itself should be a droplet. Well... huh... I just wrote one of those. All this droplet needs to do different is that instead of compressing a file, it needs to copy the prototype app and replace the shell script inside. Well... huh... I can do that with a shell script a lot more easily that in C (even Objective-C) code. So it seems all I have to do here is replace the gzip script with a script that copies the application (itself!) and puts the new script in the right place. The droplet generator iteself becomes the prototype droplet. (Being lazy, like most programmers, this was an exciting revelation.) A simple script to do this might look like this:

#!/bin/sh -e

ScriptFileName=$1; shift

DropScript="$(echo $0 | sed ‘s|\.app/.*$|\.app|')"
Destination=$(dirname "${ScriptFileName}")
DropperName="Drop"$(basename "${ScriptFileName}" | sed ‘s/\..*$//')
NewDropper="${Destination}/${DropperName}.app"

mkdir "${NewDropper}"
pax -rw . "${NewDropper}"
chmod u+w "${NewDropper}/Contents/Resources/script"
cp -f "${ScriptFileName}" "${NewDropper}/Contents/Resources/script"
chmod 555 "${NewDropper}/Contents/Resources/script"

For more information

The complete source code to DropScript is available via the Darwin CVS server at Apple, in the DropScript CVS module. You can download a pre-built executable from my home page at MIT at
http://www.mit.edu/people/wsanchez/software/.

These are a few features in DropScript which were omitted from this article for simplicity, such as:

  • The primordial shell script is actually more complicated, as it does some rudimentary error checking.
  • In order to emulate the behavior of StuffIt Expander, which is probably the most familiar drop application for Mac OS users, I added a little code to have the droplets quit after processing a file if they weren't otherwise already running.
  • There is now a mechanism by which you can specify the file types that your new droplet accepts within the shell script.
  • There is an about box and a useless preferences panel.

Some commands don't take file names as arguments and most command have some special options you might want to use. The most flexible way to create droplets would therefore be to write a shell (or perl/python/etc) script that takes file name arguments and invkoes the right command(s) with the right option(s), and use that script as the command in your droplet.


Wilfredo SÁnchez wsanchez@mit.edu is an engineering manager at KnowNow, Inc., www.knownow.com, and knows just enough about Cocoa programming to hurt himself and possibly others.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Xcode 14.3.1 - Integrated development en...
Xcode includes everything developers need to create great applications for Mac, iPhone, iPad, and Apple Watch. Xcode provides developers a unified workflow for user interface design, coding, testing... Read more
Sparkle Pro 5.0.3 - Visual website creat...
Sparkle Pro will change your mind if you thought building websites wasn't for you. Sparkle is the intuitive site builder that lets you create sites for your online portfolio, team or band pages, or... Read more
f.lux 42.2 - Adjusts the color of your d...
f.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day. Ever notice how people texting at night have that eerie blue glow? Or wake... Read more
Google Chrome 114.0.5735.90 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Pinegrow 7.5 - Mockup and design web pag...
Pinegrow (was Pinegrow Web Designer) is desktop app that lets you mockup and design webpages faster with multi-page editing, CSS and LESS styling, and smart components for Bootstrap, Foundation,... Read more
Malwarebytes 4.19.14.4978 - Adware remov...
Malwarebytes (was AdwareMedic) helps you get your Mac experience back. Malwarebytes scans for and removes code that degrades system performance or attacks your system. Making your Mac once again your... Read more
Slack 4.32.126 - Collaborative communica...
Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. Check off your to-do list and move your... Read more
DEVONthink Pro 3.9.1 - Knowledge base, i...
DEVONthink Pro is DEVONtechnologies' document and information management solution. It supports a large variety of file formats and stores them in a database enhanced by artificial intelligence (AI).... Read more
Alfred 5.1.1 - Quick launcher for apps a...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Thunderbird 102.11.2 - Email client from...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more

Latest Forum Discussions

See All

SwitchArcade Round-Up: ‘We Love Katamari...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for June 2nd, 2023. It’s Friday, so we have to mop up the remaining releases of the week. Before that though, we’ve got news of a new Nintendo first-party release that is coming very... | Read more »
‘Crossed Swords ACA NEOGEO’ Review – Inf...
Look, I don’t know if Chair’s outstanding Infinity Blade (RIP) series was at all inspired by SNK’s Crossed Swords ($3.99) or if they just had similar ideas independent of each other a couple of decades apart, but revisiting this 1991 NEOGEO title... | Read more »
Black Clover M: Rise of the Wizard King...
Garena has announced the opening of pre-registrations for its upcoming adventure RPG, Black Clover M: Rise of the Wizard King. Developed by VIC Games Studio, Black Clover M will be available in ten different languages to cater for a huge audience... | Read more »
‘Shovel Knight Pocket Dungeon’ Comes to...
Puzzle action adventure game Shovel Knight Pocket Dungeon from Yacth Club Games was announced to hit mobile through Netflix alongside its big free DLC pack that was planned for PS4, Switch, and PC platforms. The developer has now revealed that... | Read more »
Apple Arcade Weekly Round-Up: Updates fo...
I was surprised to not see new Apple Arcade games announced for June, but maybe Apple is waiting for WWDC 2023. This week, we get over half a dozen notable updates for games on the service. Episode XOXO brings in the new original story: Gilded Love... | Read more »
Rotating Run ‘n Gun Shooter ‘Roto Force’...
Roto Force is a game that has been in development for a number of years now, but as spotted in our forums today it appears this one has finally has a release date set for next month. First off, Roto Force is a run ‘n gun shooter at heart, but with... | Read more »
SwitchArcade Round-Up: ‘Etrian Odyssey O...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for June 1st, 2023. Well, here we are. The first day of the last month of the first half of the year. Where does the time go? At least as far as May is concerned, it went to Zelda for me... | Read more »
New ‘Vampire Survivors’ Update for Mobil...
The most recent Vampire Survivors (Free) news was that it is being adapted into a premium animated TV series. Today, developer poncle has pushed out a new update on iOS, Android, Xbox, and PC platforms bringing in new relics, the ability to seal... | Read more »
Former Apple Arcade Game ‘Transformers:...
Back in November 2021, Apple Arcade added Red Games Co’s PvP brawler Transformers: Tactical Arena (Free) to the service. | Read more »
No Man’s Sky Out Now on macOS After Bein...
At WWDC 2022, Apple announced that the brilliant No Man’s Sky would be hitting macOS and iPadOS “later in the year". Since then, we hadn’t heard anything about the ports, but Sean Murray started teasing an Apple-related announcement on Twitter... | Read more »

Price Scanner via MacPrices.net

Final weekend to take advantage of Xfinity Mo...
Switch to Xfinity Mobile with a new line of service, and take up to $700 off the price of a new iPhone — with qualified trade-in — through June 5, 2023. The $700 is applied to your account as credits... Read more
Shop the lowest Apple iPad prices using our a...
Our Apple award-winning iPad Price Trackers are the best place to find the latest information on iPad sales and deals. We track prices from 20+ Apple retailers, including Apple, Amazon, Best Buy,... Read more
Apple AirTags 4-pack on sale for $79, $20 off...
Verizon has Apple AirTags 4 Pack on sale for $79.99, shipped, for a limited time. That’s $20 off Apple’s MSRP. Their price is the lowest available for 4 Pack AirTags from any of the retailers we... Read more
Apple Watch SE available at Apple for up to $...
Apple has Certified Refurbished Apple Watch Series SE models available today on their online store for $40-$50 off MSRP, starting at $209. Each Watch includes Apple’s standard one-year warranty, a... Read more
Find the lowest prices on Apple MacBooks usin...
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″, 14″, and 13″ MacBook Pros along with 13″ MacBook Airs... Read more
Apple increases iPhone trade-in values ahead...
Get up to $630 on the purchase of new or refurbished iPhone at Apple using their official Trade In program. Trade in your old iPhone, and Apple will apply its appraised value toward the purchase of... Read more
11-inch M2 iPad Pros on sale for $60-$75 off...
Expercom has 128GB and 256GB WiFi 11″ iPad Pros with Apple M2 processors on sale for up to $75 off MSRP through June 6, 2023. Shipping is free. Their prices are the lowest currently available on... Read more
Mac Musings: iMac Turns 25 (Plus? The ‘Mac Po...
COMMENTARY – The month of May marks two milestones, one for Apple and the other for this writer. The iMac — Apple’s all-in-one desktop computer — is now a quarter of a century old. Not even the... Read more
Apple Studio Display with Standard Glass now...
Amazon has the standard-glass Apple Studio Display on sale for $250 off MSRP for a limited time. Shipping is free: – Studio Display (Standard glass): $1349.99 $250 off MSRP Their price is the lowest... Read more
12.9-inch WiFi iPad Pros on sale for $100 off...
Amazon has 12.9″ 128GB, 256GB, and 512GB WiFi iPad Pros with Apple M2 processors on sale for $100 off MSRP, each including free shipping. Their prices are the lowest available for these iPad Pros... Read more

Jobs Board

Endpoint Hardware Specialist - *Apple* - Th...
Description TITLE: Endpoint Hardware Specialist - Apple DATE: April 2023 DEPT: Territorial Information Technology STATUS: Non-Exempt THE SALVATION ARMY MISSION: The Read more
Security Officer - *Apple* Store - NANA Reg...
…security is our \#1 priority\. This is a public environment at the Apple Store and surrounding areas with the corresponding levels of traffic \(employees, visitors, Read more
Systems Administrator- *Apple* - NOVA Corpo...
…is seeking a Systems Administrator with experience in administering and maintaining Apple hardware and software, including but not limited to, servers, PCs (Macs), Read more
*Apple* Technician - CompuCom (United States...
…**cell** and enjoy our generous employee benefits! Our client is currently seeking an ** Apple Technician** to join their team onsite in Santa Clara, CA. This Read more
*Apple* Certified Technician - LeadingIT (Un...
Apple Certified Technician Level 1 Apple Technician Are you a perfectionist who enjoys following directions? Do you need variety and change to keep from getting Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.