According to Apple, Tiger adds over 200 new features to Mac OS X. While some of these features are simple upgrades or underlying fixes that are nearly invisible to users, one feature that users and developers alike have been looking forward to for months is Dashboard.
Dashboard is a new environment that allows users to run mini-applications called widgets. These widgets, while able to use all of the advanced features of Tiger, are simple to use and simple to develop. This is the first in a series of two articles that gives an introduction to developing Dashboard widgets. This article will focus on the basics of widget development and then go over the steps required to develop a widget that displays *nix man pages.
Dashboard widgets are a lot like web pages. They use HTML markup for user interfaces and JavaScript for programming tasks, and are written and saved as text files. On the face of it, this makes Dashboard sound like a glorified web browser, but two things make Dashboard much more than a web browser. First are the additions to JavaScript that support interaction with many of the underlying features of Tiger, and second is the ability to isolate widgets from each other and display them in a visually appealing and consistent manner.
Dashboard's hooks into OS X allowing developers to:
Run any Unix command.
Interact with programs that support script plugins, like iTunes.
Draw on the screen with Quartz drawing commands.
Load and run Internet plugins such as Preview.
Add in the features and ease of coding with JavaScript, and you have an environment that gives developers all of the tools they need to develop powerful, interesting applications. The widgets that Apple includes with Tiger show off many of the types of things that developers can do, including controlling Address Book and iTunes, downloading weather information from a remote server, and converting values between units. Apple's widgets, and the widgets in the this and the next article, only touch the surface of the types of applications a clever developer can create.
Since widgets are really just web pages, presenting them to the user can be something of a challenge. Web pages in Safari are defaulted to have white backgrounds, are usually 800 by 600 pixels and use a huge amount of screen space for toolbars. If widgets were displayed in browsers, then on an 12" iBook, one or two widgets would take up the whole screen. These two widgets could also interfere with each other and either of them could cause the browser to crash.
Apple's Dashboard environment strips the toolbars, default size, and background color from the browser and puts each widget in its own sandbox so they cannot interfere with each other. This allows widgets to run on their own and also look like individual applications to users.
Dashboard widgets are stored in one of two places: user widgets are stored in ~/Library/Widgets, while system widgets are stored in /Library/Widgets. Widgets are stored in widget packages, which are just directories with a .wdgt extension. Each widget package has to have at least three files:
Info.plist, a properties list that has the setup information for a widget.
A default background image in the PNG format.
A default HTML page to load when the widget is run.
Info.plist contains all of the information that Dashboard needs to run a widget. This information includes mappings for the default HTML page and default background image, information on the developer of the widget, and information on what the widget is allowed to do. The Info.plist file that we will use for the ManPage widget looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC
"-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AllowFullAccess</key>
<true/>
<key>AllowMultipleInstances</key>
<false/>
<key>CFBundleIdentifier</key>
<string>com.macdevcenter.widget.ManPage</string>
<key>CFBundleName</key>
<string>Man Page</string>
<key>DefaultImage</key>
<string>Default</string>
<key>Height</key>
<string>400</string>
<key>MainHTML</key>
<string>ManPage.html</string>
<key>Width</key>
<integer>400</integer>
</dict>
</plist>
|
Related Reading
Learning Unix for Mac OS X Tiger |
These properties are used to set the following for each widget:
MainHTML: The name of the default HTML file that the widget will load.
Individual widgets can contain any number of HTML, JavaScript, and CSS files,
so we need to set the one that the widget will use when it is first loaded.
DefaultImage: The name of the default PNG file that the widget will use when it is loaded. While web pages always have a background, Dashboard widgets do not. Dashboard widgets need to load an image that the HTML file will use as a background while it is loading. We will explore setting up the default image in the next section.
Width: The width of the widget. For simple widgets, this is the size of the default image. For more complex widgets, the width of the image might change during execution, so be sure to set the largest size you will need. Specifying the width basically defines the screen area that is included as part of the widget. If your width is too short, you will not have enough room for your widget; if it is too large, your widget will waste screen real estate.
Height: The height of the widget. Has all of the same criteria as the width.
CFBundleName: The name of the bundle; in this case, "ManPage." This can generally be whatever you want it to be.
CFBundleIdentifier: The reverse Internet-domain-style identifier for the bundle. This property uses a naming convention similar to that used for Java classes, and should be unique.
AllowMultipleInstances: This property specifies if you want the user to be able to have more than a single instance of this widget available. This has no affect on how the widget will run, but might be required for an individual widget. For instance, for a widget like Stickies, we would want to have multiple instances available, but for a widget that downloads the local weather, multiple instances might be overkill.
AllowFullAccess: This property specifies if you want the widget to be able to run Unix commands and access external applications. If you are using a lot of web-based features in your widget you should be careful about how you set the value for this widget.
The MainHTML, Width, Height, CFBundleName, CFBundleIdentifier, and DefaultImage properties are required. Info.plist can be edited in Apple's Property List Editor or with any text editor.
|
As I said in the "DefaultImage" section of the Info.plist overview, widgets need to have a default background image. This image is displayed while Dashboard loads a widget. For a simple widget, the easiest thing to do is to have this image be the same as the background image that you will have for the widget once it is loaded. Apple's documentation specifies that this image needs to be a PNG file. PNG files can be created with Photoshop, Fireworks, GIMP, or just about any other image-editing software.
To keep things simple, I have named the file for the ManPage project Default.png. Here is what it looks like:

While this file can be as complex or as simple as a developer would like, it is important to remember that the main purpose of this file is to display an image while the widget is loading. This allows the user to see information about the widget, the default footprint of the widget, and other visual information that the developer wants the user to see. To keep things simple for the ManPage widget, this image is the same as the as background for the widget.
MainHTML FileMainHTML is the file that provides the basic user interface and JavaScript functions for the widget. While complex widgets could include multiple HTML, CSS and .js files, for simplicity's sake, the ManPage widget consolidates all of this into one file.
While MainHTML files are basically HTML files, similar to those for regular web pages, there are some things to keep in mind while developing them. First and foremost, there is no default background for a widget. In order for the widget to have a canvas to draw on, you need to specify a background image. (I am not sure if invisible widgets would work, but it would be cool to try out.) This can be done using a div tag, like in the ManPage HTML section below.
The second thing to keep in mind is that Dashboard conforms to CSS, JavaScript, and DOM web standards. While this article is not intended to be an overview of developing CSS-based HTML pages, I will say that since Dashboard is basically a conforming browser, it is much easier to develop and debug widgets using web standards.
The MainHTML for the ManPage widget is below:
<html>
<head>
<title>Dashboard Man Page</title>
<meta http-equiv="Content-Type"
content="text/html;charset=utf-8" >
<style type="text/css">
<!--
.backStyle {
position:absolute;
width:400px;
height:400px;
left:0px;
top:0px;
z-index:0;
visibility: visible;
}
.textStyle {
font-family: Lucida Grande, Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
position:absolute;
width:111px;
height:22px;
z-index:1;
left: 128px;
top: 26px;
}
.inputStyle{
position:absolute;
width:123px;
height:23px;
z-index:1;
left: 246px;
top: 24px;
visibility: visible;
}
.outputStyle{
position:absolute;
width:324px;
height:400px;
z-index:1;
left: 33px;
top: 72px;
visibility: visible;
}
-->
</style>
<script type='text/javascript'>
<!--
// get man page
function getManPage()
{
if (document.getElementById('programName').value != null)
{
var commandLine =
'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "'
+ document.getElementById('programName').value+'"`'
+ ' | /bin/cat';
var output = widget.system(commandLine, null);
document.getElementById('outputArea').value = output.outputString;
}
}
-->
</script>
</head>
<body>
<div id="BackgroundLayer" class="backStyle">
<img src="Default.png" width="400" height="600">
<div id="TextLayer" class="textStyle">
<div align="right"> Get More Info On:</div>
</div>
</div>
<div id="InputLayer" class="inputStyle">
<input id="programName" type="text"
size="14" border="0" height="23px" width="123px"
onchange='getManPage();'>
</input>
</div>
<div id="OutputLayer" class="outputStyle">
<textarea id="outputArea" cols="42"
rows="25" readonly>
</textarea>
</div>
</body>
</html>
This is about as simple an HTML file as you can get: there is some header information, one JavaScript function, one style, and then some HTML markup. The HTML markup defines three layers: one for the background, one for a box where a user can input the name of the *nix command that they want a man page for, and one for the output of the man page. To trigger the retrieval of the man page, when the value in the programName input box has changed, it calls the getManPage() function via the onchange attribute.
The getManPage() function is where all the heavy lifting of this widget happens. Here is what happens, line by line:
if (document.getElementById('programName').value != null){
This checks to make sure the programName input element actually contains text. We need to do this because onchange will trigger the getManPage method any time the text changes, including if it is all deleted.
var commandLine =
'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "'
+ document.getElementById('programName').value+'"`'
+ ' | /bin/cat';
This defines the shell command that we will call to get the man
page. The command looks pretty complex, but like most shell commands, it is
actually pretty easy once it is broken down. The first thing to look at is
the
middle section, because this is the first thing the shell will process.
`man -w "' + document.getElementById('programName').value +
'`
We are building a string here that gets the man page for the programName that
we entered. If we input ls, then this command would be:
`man -w ls `
The back ticks (`s) that surround the command tell the shell
to
replace the back-ticked expression with the output of the expression before
running the rest of the command line.
The output from the man command, once it is replaced on the
command line, is sent to the groff utility via:
"groff -mandoc -Tascii -P-b -P-c" +
This utility is used to format text for output to various devices. We
need
to call this command because the output from man is formatted
to
be displayed on a terminal screen, including bold and underline control
characters. If we did not strip these out, the output would look very
strange
in the HTML text area in which we are placing it. The parameters to
groff are set to interpret the input as output from the man command and the output as ASCII.
The last part of the command line is:
"| cat "
This forces the output of the whole command to go to stdout.
Anyone who has used
any *nix shell will tell you that this is a weird command to include, and
I
agree.
I needed to include it because of the way that Dashboard's JavaScript
widget.system command handles output to stdout (we
will go over widget.system in the next section).
var obj = widget.system(commandLine ,null);
This command is used to run the command that we defined before in a *nix shell. The first argument is the commandLine string that we built before. The second argument is a mystery. Apple only provides one or two samples of this command in any of the documentation that comes with the pre-release version of Tiger and on the developer site. I have Googled the Web and searched all of Apple's documentation to see what exactly the null specifies, but I have found nothing. Both of Apple's examples call it with a null value, so I do the same.
document.getElementById('outputArea').value =
obj.outputString;
This command takes the outputString from the widget.system call and puts the value in the outputArea element.
While this is not the most complex or useful widget in the world, it does show off some of the capabilities of what Dashboard can do. Since Dashboard gives full access to the *nix shell, it is easy to imagine a widget calling a Perl script that makes database calls or interacts with a local CVS server. This means that, beyond the obvious uses of creating end-user widgets, Dashboard provides developers with a rapid prototype environment where they can try out new ideas with only a little bit of coding required.
The next article will include information on debugging and testing widgets, links to some sites that have sprouted up with widgets to download and bulletin boards for widget developers, and a how-to on writing a widget that uses JavaScript's object to retrieve information from remote web XML servers.
If you are looking for more information on Dashboard widget development, check out Apple's Developing Dashboard Widgets page. The page is kind of sparse, but gives some insight to developing widgets
Andrew Anderson is a software developer and consultant in Chicago who's been using and programming on the Mac as long as he can remember.
Return to MacDevCenter.com.
Copyright © 2009 O'Reilly Media, Inc.