Anyone can create their very own theme for Bowtie — well, anyone with a little experience in HTML, CSS, and JavaScript!
Bowtie is based on the powerful WebKit engine, the same one that powers Safari and Dashboard. In fact, creating a Bowtie theme is akin to creating a Dashboard widget: it’s as simple as creating a standard web page, only you also provide a bit of metadata (in Info.plist), and have a handy JavaScript API to access special Bowtie features.
To get started, create a folder for your theme. It is recommended that this folder share your theme’s name, and required that it have the extension “bowTie” (for example, the theme named iPhoney is contained in a folder called iPhoney.bowTie). If you have Bowtie installed, this folder should automatically become a “package”, or a folder that’s trained to act as a single file. To get into it from here, Control + Click it, and select “Show Package Contents”. Alternatively, you can just remove the “.bowTie” extension until you’re ready to test it.
When Bowtie launches, it searches three places for themes:
You should never place your own themes in the internal themes directory, and only sparingly use the System Application Support directory (for themes that you want accessible to every user on your system). That leaves us with the User Application Support directory, so we’ll move our theme there for now.
Bowtie themes contain metadata in an Info.plist file, the de facto standard for OS X package metadata. Info.plist is a property list file, which is simply an XML file using Apple’s Property List syntax. If you have the Developer tools (ie, Xcode) installed, you already have an application on your system called Property List Editor, which you can use to help you create these files.
If you don’t have this application, you can create one in any text editor — the basic syntax is shown below:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BTMainFile</key>
<string>index.html</string>
<key>BTWindowWidth</key>
<integer>220</integer>
</dict>
</plist>
When using Property List Editor, numbers are specified using the “Number” type; if you create one by hand, you must distinguish between integers, floats, etc. For Bowtie’s purposes, only integers should be used.
The Theme API is what you will use to get your theme to interact with Bowtie properly. For this, three mechanisms are provided for you: a set of keys in your Info.plist file; and two JavaScript objects, “iTunes” and “Bowtie”.
BTMainFile (string):
This key should contain the name of the main HTML file that makes up your theme. For instance, if you call your theme’s HTML file “index.html”, set this to “index.html”.
BTThemeName (string):
This is the name of your theme (example, “iPhoney”).
BTThemeArtist (string):
This is the artist of your theme (that’s you!). Just put your name here, unless you have something creative you want to specify.
BTArtistURL (string):
This is the URL to the artist’s website. Credit where credit is due. ;)
BTThemeIdentifier (string):
The “identifier” is a common idiom in OS X development that allows systems to uniquely identify packages. Ordinarily, this is done by using a reverse domain format, followed by the name of the package. For instance, if my website is mattpat.net (we use domain names because they are guaranteed to be unique), and my theme is called “My Theme”, my identifier would probably be “net.mattpat.My Theme”. Technically, this can be anything, as long as you can reasonably guarantee that it will be unique to your theme.
BTThemeVersion (string):
This is the version of your theme. It allows people to distinguish between different copies of the same theme after files have been changed.
BTThemePreviewImage (string):
This is the name of the image file that you are using as a preview of your theme; it is displayed in Bowtie’s Preferences window when your theme is selected. It can refer to any image file that OS X recognizes, and should be 450x300 (it will be centered if smaller, scaled down if larger). We recommend you call it “preview.png”.
BTWindowWidth (integer):
This is the width (in pixels) of the WebKit canvas that will be created for your controller. It can be larger than your theme if you would like (the background is transparent), but if it is smaller, you may run into issues with clipping and accidental scrolling. Try to make your window only slightly larger than your theme — the larger the window, the more awkward it is to take automatic screenshots, and the more processing power is used (however slightly).
BTWindowHeight (integer):
This is the height (in pixels) of the WebKit canvas that will be created for your controller. See above for explanation.
BTWindowMode (string from “desktop”, “normal”, “top”; optional):
The window mode of your theme describes how your window will behave in the shuffle of other windows. If you enter “desktop”, your controller will float below all other windows; if you choose “normal”, it will behave as any normal application; if you choose “top”, your controller will always float on top of all other windows. “normal” is assumed if you do not provide this key.
BTSpacesBehavior (string from “one”, “all”, “jump”; optional):
Bowtie allows you to control how your theme should interact with Spaces. If you choose “one”, your controller will behave like any normal application, only appearing on one space, and switching spaces when the application is activated; if you choose “all”, your controller will appear on all spaces automatically; if you choose “jump”, your controller will only appear on one space, but will move to the active space when activated (rather than changing spaces to see your application). “all” is assumed if you do not provide this key.
BTArtworkWidth (integer, optional):
This specifies the width of the artwork that is passed to your artwork function. iTunes album art is typically 600x600 in size. However, slipstreaming a 600x600 piece of artwork into a page adds a few seconds of lag to your application, and consumes additional memory, which is often undesirable. By specifying the dimensions of your artwork here, Bowtie can resize the artwork before sending it to your theme, which often greatly improves responsiveness. A width of 175 is assumed if you do not provide this key, which should be suitable for most controllers.
BTArtworkHeight (integer, optional):
This specifies the height of the artwork that is passed to your artwork function. A height of 175 is assumed if you do not provide this key, which should be suitable for most controllers. See above for explanation.
BTTrackFunction (string, optional):
This is the name of the JavaScript function (no parentheses) that should be called when the track changes. It must accept a single parameter, which will be an object of type “BTWrapper” (API explained below) with information about the current track. You are free to do your own iTunes polling using a combination of the “iTunes” API object detailed below, and JavaScript timers; however, in testing, JavaScript timers block interface interaction, and result in a much less responsive interface than allowing Bowtie to poll iTunes for you. Trust us, it’s a win-win situation: implement a track update function. If this key is not provided, it will be assumed that you do your own polling.
BTArtworkFunction (string, optional):
This is the name of the JavaScript function (no parentheses) that should be called when the album artwork changes. It must accept a single parameter, which will be the URL to the pre-sized album artwork for the current track (dimensions are taken from BTArtworkWidth and BTArtworkHeight, or assumed as 175x175 if not specified). Again, you may poll for these changes yourself, but we’ve found that it is much more efficient to allow Bowtie to notify you when these changes occur. If this key is not provided, it will be assumed that you do your own polling.
BTStatusFunction (string, optional):
This is the name of the JavaScript function (no parentheses) that should be called when it is time to update interface status information (including rating, player position, play state, volume, etc.). It accepts no parameters — you must check the relevant information yourself using the “iTunes” API object. This function is nothing more than a function that is called every 1.0 seconds; the reason it is provided is that (as has been previously stated) Bowtie timers are much more efficient than JavaScript timers, and do not block interface interaction. You do not need to implement a status function if your controller only displays artwork and track information. If this key is not provided, it will be assumed that a status function is not used.
iTunes.iTunesRunning() — bool
This method returns a boolean value indicating whether or not iTunes is currently running. This is typically unnecessary, as your window will only be displayed when iTunes is running, but is provided as a precaution for developers who wish to use it.
iTunes.canShow() — bool
This methods returns a boolean value indicating whether or not Bowtie is capable of making iTunes the active window. This will always return true for local iTunes libraries, but will return false for remote libraries accessed via Tunage.
iTunes.show() — void
This method will make iTunes the active application, and bring it to the front. The proper syntax for using this method is as follows:
if (iTunes.canShow()) iTunes.show();
iTunes.play() — void
This method sends a play command to iTunes.
iTunes.pause() — void
This method sends a pause command to iTunes.
iTunes.stop() — void
This method sends a stop command to iTunes.
iTunes.playPause() — void
This method sends a play/pause command to iTunes. This is the preferred method for controlling music playback.
iTunes.nextTrack() — void
This method sends a “next track” command to iTunes.
iTunes.previousTrack() — void
This method sends a “previous track” command to iTunes.
iTunes.shuffle() — integer
This method returns an integer value indicating whether or not “shuffle” is turned on for the current playlist. 0 indicates off, 1 indicates on. Call this from within your status function to update a shuffle button.
iTunes.setShuffle([integer] newShuffleState) — void
This method sets the shuffle state of the current playlist. It accepts a single parameter, “newShuffleState”, an integer: 0 indicates off, 1 indicates on.
iTunes.repeat() — integer
This method returns an integer value indicating the state of repeat for the current playlist. 0 indicates off, 1 indicates “repeat all”, 2 indicates “repeat single track”. Call this from within your status function to update a repeat button.
iTunes.setRepeat([integer] newRepeatState) — void
This method sets the repeat state of the current playlist. It accepts a single parameter, “newRepeatState”, an integer: 0 indicates off, 1 indicates “repeat all”, 2 indicates “repeat single track”.
iTunes.rating() — integer
This method returns an integer value indicating the rating of the current track on a scale from 0 to 100. Each “star” in iTunes is represented by 20; half-stars can be represented by 10. Call this from within your status function to update a rating display.
iTunes.ratingStars() — string
This method returns a string representation (using Unicode “star” and “half” characters) of the rating of the current track. This is a convenience method — you can easily implement your own version using iTunes.rating(). Call this from within your status function to update a rating display.
iTunes.setRating([integer] newRating) — void
This method sets the rating of the current track. It accepts a single parameter, “newRating”, an integer ranging from 0 to 100. Each “star” in iTunes is represented by 20; half-stars can be represented by 10.
iTunes.volume() — integer
This method returns an integer value indicating the volume of iTunes on a scale from 0 to 100. Call this from within your status function to update a volume slider.
iTunes.setVolume([integer] newVolume) — void
This method sets the volume of iTunes. It accepts a single parameter, “newVolume”, an integer ranging from 0 to 100.
iTunes.playState() — integer
This method returns an integer value indicating the play state of iTunes; 0 indicates stopped, 1 indicates playing, 2 indicates paused. The “fast-forwarding” and “rewinding” play states of iTunes are ignored, and will be returned as 1 (playing). Call this method from within your status function to update a play/pause button, for instance.
iTunes.playerPosition() — integer
This method returns an integer value indicating the position of the player in iTunes, represented by the number of seconds into the current track. Call this method from within your status function to update a progress indicator.
iTunes.setPlayerPosition([integer] newPlayerPosition) — void
This method changes the position of the player in iTunes. It accepts a single parameter, “newPlayerPosition”, an integer representing the number of seconds into the current track that the playhead should be set.
iTunes.currentTrack() — BTWrapper (see interface below)
This method returns a BTWrapper containing information on the current track in iTunes. Its results are automatically passed as the first parameter to your track update function. You need not call it yourself if you use a track update function.
iTunes.artwork([integer] width, [integer] height) — string
This method returns a string containing the URL to the artwork of the current track, pre-sized using the width and height (integers) provided as parameters. Its results (using parameters specified in your Info.plist, or 175x175 otherwise) are automatically passed as the first parameter to your artwork update function. You need not call it yourself if you use an artwork update function.
iTunes.fullArtwork() — string
This method returns a string containing to the URL to the artwork of the current track at its original size. Please avoid using this method whenever possible, as large artwork requires additional processing time — use instead iTunes.artwork(width, height).
iTunes.uniqueString() — string
This method returns a string representation of the persistent ID of the current track. This is a useful method to use when checking for track changes, should you decide to implement your own polling mechanism. This method is practically useless if you use a track update function (recommended).
Bowtie.version() — string
This method returns a string representation of the version of Bowtie. Use this method to selectively enable and disable features of your theme, depending on what is available to you.
Bowtie.currentFrame() — array
This method returns the current frame of the window used to represent your controller, in the order: [x-offset, y-offset, width, height]. X- and Y-offsets are measured using the Quartz coordinate system, from the bottom left corner of the screen. Keep this in mind, as distances within your theme are measured using the standard CSS coordinate system, from the top left corner.
Bowtie.setFrame([float] xOffset, [float] yOffset, [float] width, [float] height) — void
This method changes the frame of the window used to represent your controller. You can use this to change the size of your window, or in combination with Bowtie.currentFrame() to implement a custom window dragging mechanism (a window dragging mechanism has been prepared for you in the sample project). As previously stated, keep in mind that X- and Y-offsets are measured using the Quartz coordinate system (from the bottom left corner).
Bowtie.setPreferenceForKey([string] value, [string] key) — void
This method sets a theme-specific preference for a given key. For instance, if your theme supports two different size modes — an expanded mode, and a collapsed mode — you might want that setting to persist between launches of Bowtie. Using this method, you could set a preference (ie, Bowtie.setPreferenceForKey('expanded', 'sizeMode')
) based on your user’s preference, and then retrieve it with preferenceForKey
when needed. Note that, at this time, only strings can be stored — be sure to first cast your data into a string before storing.
Bowtie.preferenceForKey([string] key) — string
This method retrieves a theme-specific preference based on the given key. In the example above, you would retrieve the size mode with Bowtie.preferenceForKey('sizeMode')
. This method returns null if the preference has not been set; you can use that fact to provide default values thusly: (Bowtie.preferenceForKey('myKey') != null) ? Bowtie.preferenceForKey('myKey') : 'default value'
.
[BTWrapper] obj.property([string] prop) — (multiple return types)
The BTWrapper object is used to wrap dictionaries (“associative arrays”, or just normal objects in JavaScript) used in Bowtie for access via WebKit. It has a single method, property, which takes a single parameter, “prop”, which is a string representing the requested property. The return type varies depending on the property requested. For instance, BTWrapper’s wrapping track information contain the following properties:
The best way to become familiar with Bowtie is by experimenting with the sample project, which includes pre-made scripts that you can use to perform common functions in your own controller. The sample project is a tidied version of the Heads Up Mini controller, by Laurent Baumann.