Archive for January, 2010

MacRuby 0.5 final is out

After going through two betas, MacRuby 0.5 final is now released and can be downloaded by clicking on the icon below:


MacRuby 0.5

Don’t worry about having MacRuby and Ruby 1.8.x or 1.9 installed, MacRuby is namespaced and won’t affect your current Ruby installations, just download and launch the installer. (Note: The build was compiled for SnowLeopard only)

You can read all the details of the release on the MacRuby website.

So what changed since 0.4? Too many things for me to list them here but basically 0.5 uses LLVM to compile code and make MacRuby faster and integrate better with the Obj-c runtime. However since the last beta, here is what changed:

  • HotCocoa is now a separate gem
  • improved AOT compilation
  • Grand Central Dispatch support – use all your cores without the pain of threads. Read this post for more info.

0.5 is a solid release which I consider production ready, I personally wrote a few of small Cocoa apps in MacRuby and everything has been working very well. Of course, I’m also excited about the new stuff in 0.6 trunk like the debugger previewed a few weeks ago: http://merbist.com/2010/01/18/how-to-detect-cylons-with-macruby/ but also some drastic changes in the primitive classes that I might cover later on.

Finally, people are asking if the iPad will be able to run apps running in MacRuby. Unfortunately, the current answer is: no. The two issues with the IPhone/iP*d OS are the lack of Garbage Collector and support for BridgeSupport (needed to define CocoaTouch constants available from MacRuby). However, this matter is being discussed on the mailing list and progress is made by contributors (the core team primarily focusing on the desktop).

That is going to be an exciting Ruby week as MacRuby 0.5 is now out and Rails 3 beta/RC0 is expected really soon.

9 Comments

How to detect Cylons with MacRuby

Over the weekend, MacRuby’s trunk became version 0.6 and the bug fixing is currently done in both the 0.5 branch and trunk. Based on MacRuby’s usual release cycle I would expect a 0.5 beta3 or 0.5 final to be released soon so most of the work can be focused on trunk.

I’ll let you check on the TODO list to see what was done in 0.5 and what is in the plan for 0.6.

However, there is one feature in 0.6 that I know lots of you will just love! The good news is that Laurent already committed a very early version of his work so I figured, I should share the good news with you:

Introducing MacRuby’s debugger!

If you were expecting to read: “a Cylon detector!”, keep on reading.

Again, this feature is in really early development since it’s scheduled for 0.6 and 0.5 final is not out yet. But if you install MacRuby using the nightly builds or build from trunk, you can already play with the debugger.

Let me give you a really quick tour of the debugger.

BSG75Logo posted by Matt Aimonetti

Let’s imagine that we were given the task to debug the cylon detector written by Gaius Baltar which looks like that:

characters = %w{Adama Apollo Baltar Roslin StarBuck Six}
 
def cylon?(character)
  false
end
 
characters.each do |character|
  if cylon?(character)
    puts "#{character} is a Cylon!"
  else
    puts "#{character} is not a cylon."
  end
end

Here is what happens when I execute the script:

$ macruby cylon_detector.rb 
Adama is not a cylon.
Apollo is not a cylon.
Baltar is not a cylon.
Roslin is not a cylon.
StarBuck is not a cylon.
Six is not a cylon.

The only problem is that we all know that Six is a Cylon, the detector isn’t working right so let’s debug it:

$ macrubyd cylon_detector.rb
Starting program.
cylon_detector.rb:1> b cylon_detector.rb:8 if character == 'Six'
Added breakpoint 1.
cylon_detector.rb:1> c
Adama is not a cylon.
Apollo is not a cylon.
Baltar is not a cylon.
Roslin is not a cylon.
StarBuck is not a cylon.
cylon_detector.rb:8> p cylon?(character)
=> false
cylon_detector.rb:8> p "This detector is broken!"
=> "This detector is broken!"
cylon_detector.rb:8> p def cylon?(character); character == 'Six'; end
=> nil
cylon_detector.rb:8> p cylon?(character)
=> true
cylon_detector.rb:8> p cylon?('Matt')
=> false
cylon_detector.rb:8> c
Six is a Cylon!
Program exited.

The first thing we do is to add a conditional breakpoint:

b cylon_detector.rb:8 if character == 'Six'

Basically, the debugger will add a breakpoint at line 8 which will only be active when the value of ‘character’ is equal to ‘Six’.
Now that the breakpoint added, we can continue the program execution and just wait until we reach the defined condition.

cylon_detector.rb:1> c

Once we reach the breakpoint, we evaluate the result of “cylon?(character)” by using the p command. We see that the result is “false” when we know for sure that it should be true since the value of the character variable is ‘Six’ and she is a cylon. At this point, you might have guessed that somewhat acted as a cylon agent and I pretended to fix the problem by overwriting the “cylon?” method:

cylon_detector.rb:8> p def cylon?(character); character == 'Six'; end

Now that the method is overwritten, I can check that Six is recognized as being a cylon:

cylon_detector.rb:8> p cylon?(character)
=> true

and also check that I am not detected a cylon:

cylon_detector.rb:8> p cylon?('Matt')
=> false

I can now continue the execution of the program and see that Six is detected as a Cylon!

Of course this is just a very early version of the debugger and we will see lots of improvement in the next few weeks. Who knows someone might even create a GUI for the debugger and/or a Xcode integration.

Anyway, the point being that MacRuby developers should expect a lot of awesome stuff coming up their way soon. (also be careful about the skin jobs around you, cylon detectors can’t be trusted!)

, ,

3 Comments

Controlling iTunes with MacRuby

Since Mac OS X v10.5, Apple added a technology called Scripting Bridge which allows to control and communicate with scriptable applications such as Mail, iChat or iTunes.

A few weeks back, I showed how to control iChat with MacRuby. This time I’m going to show you how to control iTunes.

Here is a small script that I wrote to wake me up in music every morning.

#!/usr/local/bin/macruby
framework 'Foundation'
framework 'ScriptingBridge'
 
itunes = SBApplication.applicationWithBundleIdentifier("com.apple.itunes")
load_bridge_support_file 'iTunes.bridgesupport'
itunes.run
 
class SBElementArray
  def [](value)
    self.objectWithName(value)
  end
end
 
itunes.stop
playlist = itunes.sources["Library"].userPlaylists["morning"]
playlist.playOnce(false) if playlist

The idea is that I have a Mac Mini streaming music through speakers connected to an AirportExpress in my bedroom.

Let’s go through the script quickly.

We start by loading two frameworks, Foundation and ScriptingBridge.
Now that we have ScriptingBridge loaded, we can control iTunes. To do that, we use:
SBApplication.applicationWithBundleIdentifier(“com.apple.itunes”)
We then load a bridgesupport file that contains the enumerated constants from the iTunes scriptable dictionary.

We make sure iTunes is running by calling #run on the application object.

Before using iTune scriptable interface, we are making the API a bit nicer, it’s totally unnecessary but it makes our code look better.

itunes.sources returns an instance of SBElementArray which is not really an array nor a hash.

The rest of the code is pretty simple, we find the library, find the playlist called ‘morning’ and play it if found.

So you might wonder two things:

  • What is the iTunes.bridgesupport file?
  • How do you know what methods are available to control iTunes?

bridgesupport file

The bridgesupport file is important since it defines the required constants.
Apple provides a metadata generator called gen_bridge_metadata which generates a bridgesupport file.

Here is what the documentation says:

$ man gen_bridge_metadata
NAME
gen_bridge_metadata -- Objective-C Bridges Metadata Generator
 
SYNOPSIS
gen_bridge_metadata [options...] headers...
 
DESCRIPTION
gen_bridge_metadata is a tool that generates bridging metadata information for a given framework or set of head-
ers. The Objective-C bridges supported in Mac OS X, such as RubyCocoa (Ruby) and PyObjC (Python), read this
information at runtime.
 
Metadata files describe the parts of an Objective-C framework that the bridges cannot automatically handle. These
are primarily the ANSI C elements of the framework -- functions, constants, enumerations, and so on -- but also
include special cases such as functions or methods that accept pointer-like arguments. These special cases must
be manually specified in separate files called exceptions. The gen_bridge_metadata tool can then read in the
exceptions file when it generates the framework metadata.
 
The file extension used for metadata files should be .bridgesupport.
 
Certain elements, such as inline functions, cannot be described in the metadata files. It is therefore required
to generate a dynamic library in order to make the bridges use them. The gen_bridge_metadata tool can take care
of that for you.
 
The file extension for the dynamic libraries should be .dylib.
 
You should install metadata files in one of three filesystem locations. For example, for a framework named
MyFramework that is installed as /Library/Frameworks/MyFramework.framework, you can install the
MyFramework.bridgesupport and MyFramework.dylib files in one of the following possible locations, in order of
priority:
 
o   /Library/Frameworks/MyFramework/Resources/BridgeSupport
 
o   /Library/BridgeSupport
 
o   ~/Library/BridgeSupport

The problem is that we don’t have a framework or header file to generate a bridgesupport file for.
So, what we need a header file for iTunes, turns out we have a tool to do that:

$ sdef /Applications/iTunes.app | sdp -fh --basename iTunes

I won’t go in the details of what sdef and sdp do, just check their manual page.
Running the command above will create a iTunes.h which we can use to create a bridgesupport file.
Here is the generated header file: http://gist.github.com/279657

Now, let’s create a bidgesupport file:

$ gen_bridge_metadata -c '-I.' iTunes.h > iTunes.bridgesupport

An that’s how we get the bridgesupport file. (see file: http://gist.github.com/279698)

iTunes Documentation

The easiest way to understand what’s available to you is to open iTunes’ dictionary in the AppleScript Editor.

iTunes API by Matt Aimonetti

Otherwise you can study the iTunes.h file.

I wrote a very dumb parser to give you an idea of the methods and properties available when controlling iTunes via ScriptingBridge, here is the  output:

Class: iTunesPrintSettings
Properties:
copies (the number of copies of a document to be printed)
collating (Should printed copies be collated?)
startingPage (the first page of the document to be printed)
endingPage (the last page of the document to be printed)
pagesAcross (number of logical pages laid across a physical page)
pagesDown (number of logical pages laid out down a physical page)
errorHandling (how errors are handled)
requestedPrintTime (the time at which the desktop printer should print the document)
printerFeatures (printer specific options)
faxNumber (for fax number)
targetPrinter (for target printer)
 
Method: printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme
Returned: void
Print the specified object(s)
----
Method: close
Returned: void
Close an object
----
Method: delete
Returned: void
Delete an element from an object
----
Method: duplicateTo:(SBObject *)to
Returned: SBObject
Duplicate one or more object(s)
----
Method: exists
Returned: BOOL
Verify if an object exists
----
Method: open
Returned: void
open the specified object(s)
----
Method: playOnce:(BOOL)once
Returned: void
play the current track or the specified track or file.
----
 
Class: iTunesApplication
Properties:
currentEncoder (the currently selected encoder (MP3, AIFF, WAV, etc.))
currentEQPreset (the currently selected equalizer preset)
currentPlaylist (the playlist containing the currently targeted track)
currentStreamTitle (the name of the current song in the playing stream (provided by streaming server))
currentStreamURL (the URL of the playing stream or streaming web site (provided by streaming server))
currentTrack (the current targeted track)
currentVisual (the currently selected visual plug-in)
EQEnabled (is the equalizer enabled?)
fixedIndexing (true if all AppleScript track indices should be independent of the play order of the owning playlist.)
frontmost (is iTunes the frontmost application?)
fullScreen (are visuals displayed using the entire screen?)
name (the name of the application)
mute (has the sound output been muted?)
playerPosition (the player’s position within the currently playing track in seconds.)
playerState (is iTunes stopped, paused, or playing?)
selection (the selection visible to the user)
soundVolume (the sound output volume (0 = minimum, 100 = maximum))
version (the version of iTunes)
visualsEnabled (are visuals currently being displayed?)
visualSize (the size of the displayed visual)
 
Method: browserWindows
Returned: SBElementArray
----
Method: encoders
Returned: SBElementArray
----
Method: EQPresets
Returned: SBElementArray
----
Method: EQWindows
Returned: SBElementArray
----
Method: playlistWindows
Returned: SBElementArray
----
Method: sources
Returned: SBElementArray
----
Method: visuals
Returned: SBElementArray
----
Method: windows
Returned: SBElementArray
----
Method: printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme
Returned: void
Print the specified object(s)
----
Method: run
Returned: void
run iTunes
----
Method: quit
Returned: void
quit iTunes
----
Method: add:(NSArray *)x to:(SBObject *)to
Returned: iTunesTrack
add one or more files to a playlist
----
Method: backTrack
Returned: void
reposition to beginning of current track or go to previous track if already at start of current track
----
Method: convert:(NSArray *)x
Returned: iTunesTrack
convert one or more files or tracks
----
Method: fastForward
Returned: void
skip forward in a playing track
----
Method: nextTrack
Returned: void
advance to the next track in the current playlist
----
Method: pause
Returned: void
pause playback
----
Method: playOnce:(BOOL)once
Returned: void
play the current track or the specified track or file.
----
Method: playpause
Returned: void
toggle the playing/paused state of the current track
----
Method: previousTrack
Returned: void
return to the previous track in the current playlist
----
Method: resume
Returned: void
disable fast forward/rewind and resume playback, if playing.
----
Method: rewind
Returned: void
skip backwards in a playing track
----
Method: stop
Returned: void
stop playback
----
Method: update
Returned: void
update the specified iPod
----
Method: eject
Returned: void
eject the specified iPod
----
Method: subscribe:(NSString *)x
Returned: void
subscribe to a podcast feed
----
Method: updateAllPodcasts
Returned: void
update all subscribed podcast feeds
----
Method: updatePodcast
Returned: void
update podcast feed
----
Method: openLocation:(NSString *)x
Returned: void
Opens a Music Store or audio stream URL
----
 
Class: iTunesItem
Properties:
container (the container of the item)
index (The index of the item in internal application order.)
name (the name of the item)
persistentID (the id of the item as a hexidecimal string. This id does not change over time.)
 
Method: id
Returned: NSInteger
the id of the item
----
Method: printPrintDialog:(BOOL)printDialog withProperties:(iTunesPrintSettings *)withProperties kind:(iTunesEKnd)kind theme:(NSString *)theme
Returned: void
Print the specified object(s)
----
Method: close
Returned: void
Close an object
----
Method: delete
Returned: void
Delete an element from an object
----
Method: duplicateTo:(SBObject *)to
Returned: SBObject
Duplicate one or more object(s)
----
Method: exists
Returned: BOOL
Verify if an object exists
----
Method: open
Returned: void
open the specified object(s)
----
Method: playOnce:(BOOL)once
Returned: void
play the current track or the specified track or file.
----
Method: reveal
Returned: void
reveal and select a track or playlist
----
 
Class: iTunesPlaylist
Properties:
duration (the total length of all songs (in seconds))
name (the name of the playlist)
parent (folder which contains this playlist (if any))
shuffle (play the songs in this playlist in random order?)
size (the total size of all songs (in bytes))
songRepeat (playback repeat mode)
specialKind (special playlist kind)
time (the length of all songs in MM:SS format)
visible (is this playlist visible in the Source list?)
 
Method: tracks
Returned: SBElementArray
----
Method: moveTo:(SBObject *)to
Returned: void
Move playlist(s) to a new location
----
Method: searchFor:(NSString *)for_ only:(iTunesESrA)only
Returned: iTunesTrack
search a playlist for tracks matching the search string. Identical to entering search text in the Search field in iTunes.
----
 
Class: iTunesAudioCDPlaylist
Properties:
artist (the artist of the CD)
compilation (is this CD a compilation album?)
composer (the composer of the CD)
discCount (the total number of discs in this CD’s album)
discNumber (the index of this CD disc in the source album)
genre (the genre of the CD)
year (the year the album was recorded/released)
 
Method: audioCDTracks
Returned: SBElementArray
----
 
Class: iTunesDevicePlaylist
Method: deviceTracks
Returned: SBElementArray
----
 
Class: iTunesLibraryPlaylist
Method: fileTracks
Returned: SBElementArray
----
Method: URLTracks
Returned: SBElementArray
----
Method: sharedTracks
Returned: SBElementArray
----
 
Class: iTunesRadioTunerPlaylist
Method: URLTracks
Returned: SBElementArray
----
 
Class: iTunesSource
Properties:
capacity (the total size of the source if it has a fixed size)
freeSpace (the free space on the source if it has a fixed size)
kind ()
 
Method: audioCDPlaylists
Returned: SBElementArray
----
Method: devicePlaylists
Returned: SBElementArray
----
Method: libraryPlaylists
Returned: SBElementArray
----
Method: playlists
Returned: SBElementArray
----
Method: radioTunerPlaylists
Returned: SBElementArray
----
Method: userPlaylists
Returned: SBElementArray
----
Method: update
Returned: void
update the specified iPod
----
Method: eject
Returned: void
eject the specified iPod
----
 
Class: iTunesTrack
Properties:
album (the album name of the track)
albumArtist (the album artist of the track)
albumRating (the rating of the album for this track (0 to 100))
albumRatingKind (the rating kind of the album rating for this track)
artist (the artist/source of the track)
bitRate (the bit rate of the track (in kbps))
bookmark (the bookmark time of the track in seconds)
bookmarkable (is the playback position for this track remembered?)
bpm (the tempo of this track in beats per minute)
category (the category of the track)
comment (freeform notes about the track)
compilation (is this track from a compilation album?)
composer (the composer of the track)
databaseID (the common, unique ID for this track. If two tracks in different playlists have the same database ID, they are sharing the same data.)
dateAdded (the date the track was added to the playlist)
objectDescription (the description of the track)
discCount (the total number of discs in the source album)
discNumber (the index of the disc containing this track on the source album)
duration (the length of the track in seconds)
enabled (is this track checked for playback?)
episodeID (the episode ID of the track)
episodeNumber (the episode number of the track)
EQ (the name of the EQ preset of the track)
finish (the stop time of the track in seconds)
gapless (is this track from a gapless album?)
genre (the music/audio genre (category) of the track)
grouping (the grouping (piece) of the track. Generally used to denote movements within a classical work.)
kind (a text description of the track)
longDescription ()
lyrics (the lyrics of the track)
modificationDate (the modification date of the content of this track)
playedCount (number of times this track has been played)
playedDate (the date and time this track was last played)
podcast (is this track a podcast episode?)
rating (the rating of this track (0 to 100))
ratingKind (the rating kind of this track)
releaseDate (the release date of this track)
sampleRate (the sample rate of the track (in Hz))
seasonNumber (the season number of the track)
shufflable (is this track included when shuffling?)
skippedCount (number of times this track has been skipped)
skippedDate (the date and time this track was last skipped)
show (the show name of the track)
sortAlbum (override string to use for the track when sorting by album)
sortArtist (override string to use for the track when sorting by artist)
sortAlbumArtist (override string to use for the track when sorting by album artist)
sortName (override string to use for the track when sorting by name)
sortComposer (override string to use for the track when sorting by composer)
sortShow (override string to use for the track when sorting by show name)
size (the size of the track (in bytes))
start (the start time of the track in seconds)
time (the length of the track in MM:SS format)
trackCount (the total number of tracks on the source album)
trackNumber (the index of the track on the source album)
unplayed (is this track unplayed?)
videoKind (kind of video track)
volumeAdjustment (relative volume adjustment of the track (-100% to 100%))
year (the year the track was recorded/released)
 
Method: artworks
Returned: SBElementArray
----
 
Class: iTunesFileTrack
Properties:
location (the location of the file represented by this track)
 
Method: refresh
Returned: void
update file track information from the current information in the track’s file
----
 
Class: iTunesURLTrack
Properties:
address (the URL for this track)
 
Method: download
Returned: void
download podcast episode
----
 
Class: iTunesUserPlaylist
Properties:
shared (is this playlist shared?)
smart (is this a Smart Playlist?)
 
Method: fileTracks
Returned: SBElementArray
----
Method: URLTracks
Returned: SBElementArray
----
Method: sharedTracks
Returned: SBElementArray
----

, ,

4 Comments

Amazon Kindle 2

For Noël, my wife got me a new toy: the Amazon Kindle 2.

Kindle vs Nook

Before my wife bought what became my new favorite gadget, we checked on the Barnes & Noble Nook.

The primary problem with the Nook was that it was out of stock. That pushed me to dig deeper and really compare both devices.

The Nook had some interesting features like wifi, small color screen and the book lending feature.

However, I don’t really care about a small, clunky extra color screen since I just want to read books. The wifi is nice… but what am I going to do with it? Lending books is a cool feature, but this feature isn’t available for all books and is apparently seriously limited.

So I decided to focus on what I really needed instead of what I could use.

  • The main purpose of an e-book is reading, page loading should be fast and the user experience should be at least as good as when reading a ‘real’ book.
  • I need to be able to have a great choice of books and if possible in multiple languages.
  • I would like to be able to read the safarionline books that I can access online.

From what I read almost everywhere, the nook is slow and a bit buggy (it’s a first generation after all) and doesn’t come with a web browser. I also checked on the ebooks available on Amazon and Barnes & Noble. Amazon has way more ebooks and that’s really what motivated my decision. The other thing is that Amazon is present world wide and I expect them to start selling more ebooks in French and Spanish.

The other thing that pushed me to buy a Kindle was the fact that I know so many people having one and being so pleased. During a recent trip to San Diego, Yehuda Katz told me about how much he loves his Kindle and he recommended a book to read. I started reading the book on my iPhone (using kindle for iPhone) and was really wanted a bigger screen.

Kindle vs iTablet

A lot of people asked me why I didn’t wait for the big announcement of the iTablet because it might be a Kindle killer.

First, the iTablet is currently just a rumor and if we would believe the rumor, the iTablet will sell around $1,000 vs $250 for a kindle. The tablet will probably do a lot of cool stuff my e-reader can’t do… but I don’t care I chose the kindle because I wanted a simple device to read book and only do that. Actually, the  kindle + iphone combo works great for me. I used to read a lot but because I have been moving so many times I started reading less because I always get rid of my books. Also, when traveling a lot, carrying your books with you is a pain. (especially now that most airlines charge you per bag)

Kindle and tech books/documentation.

If you want a kindle to read tech books, don’t get a 6″, get the kindle DX instead. Even though the 6″ natively supports PDF files, the text is usually too small and the Kindle doesn’t support zooming (yet).

The DX is perfect to read PDFs because the ratio matches the print expectations. I was thinking about converting all the Cocoa docs to a kindle friendly format since the kindle supports search etc.. But the truth is that it’s not worth it. The display on a 6″ screen is clunky, it just doesn’t feel right. I didn’t try any of the pragprog ebooks but something tells me they will just look better on the DX.

Since I wanted a kindle to read books and not browse tech documentation, I’m still really happy with 6″.

If you also have kindle or are planning on buying one soon, here are two books I’ve been enjoying reading:

Flashforward by Robert J. Sawyer

The Talent Code by Daniel Coyle

1 Comment