Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Tuesday, July 23, 2013

Using 3rd-party Erlang modules in Elixir


Calling into other people's Erlang from Elixir is pretty straightforward, but there are a couple things to be aware of. Let's start with some sample Erlang code that uses the epgsql module.


-module(pgdemo).
-compile(export_all).

-define(EPG_PATH, "/Users/alh/src/epgsql/ebin").

main() ->
    setup_paths(),
    {ok, PGConn} = pgsql:connect("localhost", "dba", [{database, "users"}]),
    {ok, Columns, Rows} = pgsql:squery(PGConn, "SELECT * FROM auth_user").

setup_paths() ->
    code:add_patha(?EPG_PATH).
Here's the same code written in Elixir:


defmodule PgDemo do
  @epg_path "/Users/alh/src/epgsql/ebin"

  def main do
    setup_paths
    {:ok, pg_conn} = :pgsql.connect('localhost', 'dba', [{:database, 'users'}])
    {:ok, columns, rows} = :pgsql.squery(pg_conn, 'SELECT * FROM auth_user')
  end

  defp setup_paths do
    Code.append_path(@epg_path)
  end
end
So what's different?
  • Because the module naming conventions are different between Elixir and Erlang, Elixir imports Erlang module names as atoms, e.g. :pgsql
  • Again because of naming conventions, Erlang atoms like database become :database and a local variable like PgConn in the Erlang version becomes pg_conn in Elixir.
  • Most (all?) Erlang functions that take a string as an argument are going to expect that string to be a char list, a list of 8-bit values ... but a typical double-quoted string in Elixir is UTF-8! We need to single-quote string literals when they are arguments to an Erlang function. If you have a UTF-8 string stored in an Elixir variable, you can convert it to a char list with the binary_to_list/1 function. For more about Elixir strings see the Getting Started Guide section 2.3.

Also, there doesn't seem to be an exact equivalent in Elixir to Erlang's -define() for defining a constant. You can use module attributes (Section 3.6) for defining string constants and they will be inlined at compile-time.


Wednesday, January 04, 2012

A Concurrency Programming Kata in Erlang


Write an Erlang program that spawns two processes. Each process should:
  1. Send a unique (pseudo-random) key/value pair to the CouchDB database. Remember the value in-process so it can be confirmed later (see Step 6.) Don't use process dictionaries for this.
  2. Send the Id of the created CouchDB record to the other process.
  3. Listen for Ids being sent to it by the other process.
  4. On receiving an Id, retrieve the Id from CouchDB.
  5. Send the retrieved value back to the other process to confirm the retrieval is correct.
  6. Confirm any values sent to it by the other process. Don't re-fetch the record from CouchDB. Don't use process dictionaries.
  7. Sleep for an interval.
  8. Goto (1).
This kata helps us to explore Erlang's concurrency primitives, which are Erlang's best, most distinctive feature.

If you're using Homebrew on OS X:


~$ brew install erlang
~$ brew install couchdb
~$ couchdb # Will run in the foreground, switch to a new Terminal for the rest
To get started using Erlang to communicate with CouchDB you'll need a couple modules, installed like this:


~$ git clone git://github.com/ngerakines/erlang_couchdb.git ; cd erlang_couchdb ; make
~$ svn checkout http://mochiweb.googlecode.com/svn/trunk/ mochiweb ; cd mochiweb ; make
Here's some sample code to load the module paths, demonstrate the CouchDB API, and verify all of those pieces are working:


-module(kata_couch).
-compile(export_all).

% Path to your erlang_couchdb clone + "/ebin"
-define(ERLANG_COUCHDB_PATH, "/Users/alh/src/erlang_couchdb/ebin").
% Path to your mochiweb checkout + "/ebin"
-define(MOCHIWEB_PATH, "/Users/alh/src/mochiweb/ebin").
-define(DBSERVER, {"localhost", 5984}).

main() ->
    setup_paths(),
    db_demo(),
    init:stop().

db_demo() ->
    erlang_couchdb:create_database(?DBSERVER, "test1"),
    {json, {struct, [{_,_}, {<<"id">>,Cid}, {_,_}]}} =
        erlang_couchdb:create_document(?DBSERVER, "test1", [{<<"keyA">>, <<"valA">>}]),
    Retr1 = erlang_couchdb:retrieve_document(?DBSERVER, "test1", binary_to_list(Cid)),
    {json, {struct, [{_,_}, {_,_}, {K,V}]}} = Retr1,
    io:format("~p : ~p~n", [K, V]).

setup_paths() ->
    code:add_patha(?ERLANG_COUCHDB_PATH),
    code:add_patha(?MOCHIWEB_PATH).
And an idiomatic sleep/1 function, taken from the Armstrong book:


sleep(T) ->
    receive
        after T ->
            true
    end.

Wednesday, August 03, 2011

perlbrew and 32-bit CPAN modules like Mac::Growl


Today I discovered perlbrew, a utility for managing multiple versions of Perl under a non-root account, in the same spirit as rvm or virtualenv. I was forced into discovering perlbrew, because installing XCode 4 onto a Mac breaks the ability to compile many CPAN modules if you stick with the computer's default Perl.

Even with perlbrew, you might still have trouble compiling CPAN modules on a Mac, if those modules need older 32-bit code. This is because perlbrew doesn't build its perls as fat binaries, it builds them as 64-bit only.

I used perlbrew to install Perl 5.14.1, and then I was trying to install Test::Continuous, which has a long chain of dependencies including Mac::Growl, Mac::Growl itself depending on Mac::Carbon. Mac::Carbon is 32-bit. I was able to work around this, but I had to (temporarily) leave the CPAN shell to do it. The steps are:

  1. Install Cocoa::Growl as a 64-bit alternative to Mac::Growl. You can use the CPAN shell for this.
  2. Install Log::Dispatch::MacGrowl. This module will recognize that you
    have Cocoa::Growl installed, but for some reason it still won't install
    via CPAN shell. But it will install if you download the tarball and build manually.
  3. Now via CPAN install Log::Dispatch::DesktopNotification.
  4. Now via CPAN install Test::Continuous.

Monday, April 11, 2011

ImageMagick, Homebrew, the NEWS.txt error


Seems like everyone has trouble installing ImageMagick, but everyone has a slightly different problem. In my case I was getting the error:

Error: No such file or directory - /usr/local/Cellar/imagemagick/6.6.7-10/share/ImageMagick/NEWS.txt
To fix this, do "brew edit imagemagick" (or "sudo brew edit imagemagick" if that's how you brew) and find these lines:

# We already copy these into the keg root
%w[NEWS.txt LICENSE ChangeLog].each {|f| (share+"ImageMagick/#{f}").unlink }
and comment out that line of Ruby. Re-run "brew install imagemagick" and it should work.

Thursday, July 01, 2010

Visual Studio: My build actions are missing


Since this seems to be a faq without an answer: In Visual Studio 2008 (and earlier?) if your project files are missing their Advanced properties - like Build Action - it probably means you were handed a "web application" that is actually a "web site" in Visual Studio parlance. You have to create a new "web application", copy over the source files, and a few other steps to complete the conversion. Some relevant links:

VS 2005: Converting a Web Site Project to a Web Application Project in Visual Studio 2005

VS 2008: Converting VS 2008 Website to Web Application

VS 2010: Converting a Web Site Project to a Web Application Project

Friday, May 14, 2010

Adobe is becoming the Jay Leno of developer tools


Adobe needs to take a page from David Letterman: "You get fired, you get another gig! You go across the street and you punish them and you make them eat your words!"

Adobe is trying to leverage public opinion - and even government intervention! - by portraying itself as the victim of a monopoly power. They start with the assumption that the iPhone is the only gateway to the mobile web, and of course it's not. The high road would be to make a Flash runtime for Android that kicks so much ass that Apple has to change their stance or be left behind in the market. This duplicitous war of words is a low road, a beggar's stance. A Jay Leno move.

Saturday, April 17, 2010

Interactive Debugger for PHP


If you're looking for a free (zero-dollar) debugger for PHP, I've stumbled onto the combination of Xdebug installed onto the server and MacGDBp on my workstation. My favorite part is that I don't need to have a copy of the source tree on my workstation to do source-level debugging. It's transferred to me on demand by the magic of DBGP.

UPDATE: If you don't mind dropping a little money, I've been happy with the debugger in PHPStorm. You still need Xdebug installed on the PHP server for PHPStorm to interact with it.

Wednesday, October 14, 2009

Can I make a UIView transition to itself?


Sure, why not?

Let's say you have a UIViewController and you want the base view to do a CATransition from itself to itself:

- (void)doTransToSelf {
    CATransition *anime = [CATransition animation];
    anime.type = kCATransitionPush;
    anime.subtype = kCATransitionFromRight;
    anime.duration = 0.5;

    UIView *mySuper = self.view.superview;
    [self.view removeFromSuperview];
    [mySuper addSubview:self.view];
    [mySuper.layer addAnimation:anime forKey:nil];
}


Or, changing this to work on a subview of self.view is trivial.

So, why would you want to do this? In my case, I have a collection of data objects and a UIView that is the user interface for editing one object. When the user is ready to move on and edit the next object in the collection, the user interface doesn't substantially change, I'm really just changing the data object that is the target of the user's edits. So I want to re-use the same view with the same set of IBOutlets, and just do the transition as a visual cue to the user - "okay, you're working on a different object now."

Sunday, June 28, 2009

Why won't my backBarButtonItem use its action?


Or, How to make a backBarButtonItem send its action to its target.

Answer: you don't.

Because a backBarButtonItem is a UIBarButtonItem, it has "target" and "action" properties, leading you to believe that if you assign values to those properties, shit will happen. Alas, shit will not happen. Or rather, the usual shit will happen, but not the extra shit you hoped would happen. The backBarButtonItem is special - no matter what you do with those properties, «target» will never receive a message for «action».

What you can do instead is assign a delegate to either your UINavigationController or UINavigationBar. Both of these classes have delegate protocols that give you opportunities to act when the stack of view controllers changes. I've been using my root view controller as the delegate object, since that one never gets popped off the stack.

Wednesday, April 15, 2009

Putting a background behind a UIView transition


When doing a setAnimationTransition: with a UIView, you can do a "flip left/right" or a "curl up/down". If you do a flip left/right, you'll usually see a blank black screen "behind" the view as it flips. (The built-in Stocks app does this.) Recently someone asked me if you could have something else back there, and I thought "huh, that's interesting, I never tried that" and we investigated. It turns out that you can, but you have to set up the view stack properly.

So let's say we have 3 UIView objects, frontView, backView, and rootView. Before the transition begins, frontView is a visible subview of rootView and backView is not yet in the view stack. You begin an animation block and then (usually) do [frontView removeFromSuperview] followed by [rootView addSubview:backView], then commit the animation block and watch the animation take place. You might think that adding another subview of rootView, one that sits below frontView, would make that view be visible behind the animation during the transition. At least, that's what I thought, but it doesn't work. I'm not sure why, but it seems that rootView and all of his subviews become "invisible" during the transition.

But, but! if you put another UIView under rootView, that view will be visible during the transition. Maybe in your project, your views are already set up that way. But if frontView and backView are immediate subviews of the main window, then of course nothing can be below the main window, and you'll need to interpose another UIView object into your view stack.

Instead of posting sample code, I'm going to suggest downloading the LocateMe sample app from ADC. It's already designed with a "main" view and a "flipside" view who are subviews of a root view, and the root view is a subview of the main window. Get the project, open the MainWindow.xib, and add a UIImageView (for example) in the main window:


Make sure the toggleView method is using UIViewAnimationTransitionFlipFromLeft(Right) in its animation block (it doesn't make sense with the curl up/down animation) and then build and run the project.

Tuesday, January 27, 2009

Rails: Database-less ActiveRecord models


It's not unusual in Rails projects to have one or two model classes that don't inherit from ActiveRecord and don't require an associated table in the database. But what if we want half of that? What if we want a model that does descend from ActiveRecord but doesn't exist in the database? I finally found some code for doing exactly that. Here's an example:


class Feedback < ActiveRecord::Base
  validates_presence_of :mailfrom, :comment

  def self.columns
    @columns ||= []
  end

  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end

  column :id
  column :mailfrom
  column :comment
  column :ipaddr
end
I derived this from the code at Caboose's blog. He says he got it from Techoweenie but I can't find it on Technoweenie's site anymore.

So anyway, why would you want this? In Rails, the ActiveRecord class is super powerful. It is of course an ORM first and foremost, but it has other great features. Also, other parts of Rails, ActionView in particular, function at their best if you can provide them with an object descended from ActiveRecord. If you don't have an ActiveRecord object, you have to use form_tag() instead of form_for(), text_field_tag() instead of text_field(), etc. You don't get to use error_messages_for() and you don't get the nifty pre-fills of the model's existing values when you error flash back to the same form.

In my case, I was creating a very simple feedback form so a visitor could provide their email address and leave a comment. The comment would be mailed to the site admins via ActionMailer. I actually had a seperate model, which was descended from ActiveRecord, that made a database record of the time, date, and IP address, but there was no requirement to store the comment itself in the database, so I thought that the Feedback object didn't need to inherit from ActiveRecord. Mistake! Cutting myself off from the best helpers that ActionView offers, something that should have been a dead-simple one-page form became a tedious slog[1] to program. I finally came to my senses and googled around until I found the above code.

I've used ActiveRecord-less models before, so at first I was surprised by my unpleasant experience. Then I thought about it and realized that none of my previous ActiveRecord-less models involved user interaction. They were all "plumbing" and I never needed, or missed, the ActionView helpers. If you're making a view for a model, and that model doesn't inherit from ActiveRecord, you're not doing it The Rails Way.

 [1] "Tedious slog" being relative. I find that Rails raises the bar for ease of (web) programming, to the point where it's actually a shock to deal with the same level of drudgery that PHP inflicts on me on a good day.

Friday, January 23, 2009

EventMachine: a TCP server module for Ruby


I just discovered EventMachine. EM is, at its least, an event-driven library around which you can build a custom TCP server. The nitty-gritty of listening, sending and receiving, and multiplexing connections is all done for you.

That's selling it short, though, because EventMachine can do a lot more. In its own words, EventMachine "provides event-driven I/O using the Reactor pattern." It can be used for other client/server models like serial ports or even interactive keyboard I/O. But it really stands out for abstracting away the low-level hassles of writing a TCP daemon.

Here's a simple Echo service built using EventMachine. (Borrowed from the README file)


require 'rubygems'
require 'eventmachine'

module EchoServer
  def receive_data data
    send_data ">>>you sent: #{data}"
    close_connection if data =~ /quit/i
  end
end

EventMachine::run {
  EventMachine::start_server "192.168.0.100", 8081, EchoServer
}
Here's an example I made. You can pass a class to EventMachine::start_server, and each new connection will create a new instance of your class. That allows you to use instance variables to record distinct state for each connection. This example buffers up the client input until the client sends a ".<CRLF>" a la SMTP, or a maximum of 10 lines.


require 'rubygems'
require 'eventmachine'

class DataBuffer < EM::Protocols::LineAndTextProtocol
  def initialize
    puts "init'ing new instance of #{self.class.to_s}"
    @line_ctr = 0
    @databuf = []
  end

  def receive_data(data)
    @databuf << data
    @line_ctr += 1
    if data == ".\r\n" || @line_ctr == 10
      if data == ".\r\n"
        @databuf.pop
      end
      send_data(@databuf.to_s)
      reset_databuf()
    end
  end

  private
  def reset_databuf
    @line_ctr = 0
    @databuf = []
  end
end

EventMachine::run {
  EventMachine::start_server "127.0.0.1", 8081, DataBuffer
}
And the best part is, I've only been playing with EventMachine for about an hour! I've only begun to understand what it can do.

So, yeah. EventMachine is badass. You should check it out.

Tuesday, January 13, 2009

The Palm Pre SDK, or, In Defense of Javascript


So the Palm Pre is generating a lot of (mostly) positive buzz.[1] There are still a lot of unknowns around it, details of the SDK are still vague, and it may yet all turn out to be sound and fury, especially since no one is allowed to touch it yet.

Most of the negativity so far has targeted the choice of Javascript as the programming language. Personally I've never had a problem with Javascript the language. I think Javascript gets a bum rap. It's a dynamically interpreted, loosely-typed, object-oriented language. Functions are first-class objects, and it has an eval() operator. Sounds a lot like... Ruby. Or Python. Or Perl. What more do you want? Closures? I suspect the Javascript hate comes out because most programmers only work with Javascript in the context of emitting Javascript code from their C# or PHP on the server side, and/or dealing with the inevitable cross-browser inconsistencies of the various Javascript runtimes. Those things do suck, but that's not the fault of Javascript the language. When I heard about an SDK with an Eclipse-based IDE that uses Javascript for programming and HTML5 for UI, my first thought was "Oh, okay, just like Flex Builder, Actionscript, and MXML."

Okay, back to the Pre: the party line is "it's just like building a web app", but we also know the Pre has features that have no analog in web app development. I'm curious to know how they handle things like the camera, or playing sounds, or playing videos. I'd guess the SDK comes with a library of Javascript APIs for interfacing with such things, but again, we don't really know yet.

 [1] I don't like that name though. "Pre", by itself, implies that something newer and better is already imminent if you wait for it. A Palm exec said they wanted to communicate the idea of "the beginning of something new." It took Thomas 5 seconds to think of "okay, how about 'Premiere'?"

Friday, January 09, 2009

iPhone: Development provisioning profiles and the Entitlements file


I found what appears to be a disconnect between Apple's provisioning documentation and what actually goes on in Xcode. If you want to build your project for development (testing) on your own device, the documentation says to select the top-level build target of your application, open the Info window, click the Build tab, and set "Code Signing Identity" -> "Any iPhone OS" to be "iPhone Developer: YourFirstName YourLastName". (Presumably you must enter your name exactly as it appears on the Team page of your developer portal.) That part works fine. Next, according to the documentation, you should set "Code Signing Provisioning Profile" to be the provisioning profile you created for your combination of name/device/application. When I go to my XCode, there is no such entry!

Instead, just above "Code Signing Identity" is an entry called "Code Signing Entitlements". What I had to do is create a new property list file in my project called Entitlements.plist. The contents of that file look like this:



Where the application-identifier is the exact App ID from your developer portal, including the 10-character prefix. And then back in the Info window, set the value of "Code Signing Entitlements" to simply be "Entitlements.plist". Drag and drop the provisioning profile onto the XCode Organizer, if you haven't already. Now you can build and push the app to your iPhone.

Monday, September 01, 2008

Google Chrome


Google Chrome

John Gruber is on target when he says it sounds more like an application runtime than a browser. But the key point is that it is an application runtime optimized for the kinds of web applications that already exist. They're not trying to make a "next generation" browser like Ubiquity or push some new kind of embedded applet ("Rublets - now you can make RIAs with Ruby!") Based on this one comic book-style presentation, Chrome is monomaniacally focused on making the existing crop of HTML+Javascript+AJAX based web applications run like lightning. And by extension, expand the viability of that platform into the near-term future. Which makes sense, considering how much Google has invested into that platform. Reading between the lines, it seems like the Google Gears developers said "here's what we could do with Gears, but the existing browsers are too slow/too crude/etc." So someone at Google said "fine, let's make our own browser so that the Gears guys can go nuts."

Sunday, May 25, 2008

Safari bookmarks and Sync Services


For my own purposes, I need to make a Cocoa program that knows about my bookmarks in Safari. Lucky for me, Safari submits its bookmarks to Sync Services, according to Syncrospector:



To complete my evil plan, I also need to know the last time I clicked on a bookmark. Lucky for me, the sync schema includes a "last visited date", according to /Applications/Safari.app/Contents/SafariSyncClient.app/ Contents/Resources/SafariClientDescription.plist:


<key>Entities</key>
<dict>
    <key>com.apple.bookmarks.Bookmark</key>
    <array>
        <string>url</string>
        <string>last visited date</string>
        <string>name</string>
        <string>position</string>
        <string>notes</string>
        <string>parent</string>
        <string>com.apple.syncservices.RecordEntityName</string>
    </array>


So I just need to write my own Sync Services client that syncs with the com.apple.bookmarks.Bookmark(s)...

Uh oh. Let's have another look at that Syncrospector screenshot. The displayed entity there is a Bookmark, but it doesn't appear to have a "last visited date" property. Neither do any of the others.

I went ahead and made my own Sync Services client, so I could poke around in there for myself, but it sure seems to be that Safari doesn't set that property on the bookmarks that it submits to the truth database (it's not required to) even though Apple's own sync schema defines such a property.

Bah. Back to the drawing board for me.

Friday, April 25, 2008

How many angels can dance on the tip of a mouse pointer? I don't care.


I used to care about piddly shit like this. The day I quit caring was the day I started wanting a Mac. (That day, incidentally, was about two years before I actually got a Mac, so don't give me any crap about rationalizing in Apple's defense.) After five years of having a Mac, I still don't care and I feel great! It's like switching from Catholic to Buddhist.

That's what not having autofocus is like to people who've been using it for the past 10 to 30 years (in my case, 20 years). BLONK! BLONK! BLONK! I'm serious. It's that bad. Not exaggerating even a tiny bit.

Bull. You're exaggerating quite a bit, because the software isn't behaving inside your narrow definition of "correctness."

(If you skip down past the hyperbole to the part where he actually starts programming, there is some interesting stuff.)

Saturday, March 15, 2008

Creating a menu-based SIMBL plugin


There are a hundred tutorials on creating nib-based Cocoa applications. And there's at least one good write-up on how to make a Cocoa bundle that can be loaded by SIMBL. SIMBL has taken on increased importance with the release of Leopard, because InputManagers can no longer be installed in user folders. They must be installed at the system level in /Library/InputManagers. However, if you or your admin install just SIMBL at the system level, SIMBL acts as a fine-grained meta-manager, loading SIMBL-compatible plugins on a per-user and per-application basis.

Normally Cocoa bundles don't include nibs, but you can add them. The CULater wiki doesn't go so far as to guide you thru adding a nib to your Cocoa bundle, or how to attach the nib-generated menus to your target application. Since we can't edit the nibs of the target application, we have to install our nib contents programatically using Objective-C.

So that's what we're going to do today. I'm going to assume you already have SIMBL installed on your computer, and that you are at least novice-level with XCode and Cocoa. I specifically cover the differences between Interface Builder 2 and Interface Builder 3, mainly to point out how much IB3 has been improved.

1) Open XCode and start a new Cocoa Bundle. Let's call it "George".

2) Before we create any classes, we'll use Interface Builder to make the menu. The menu is going to have a single "About..." menu entry. A little later when we load the bundle, we'll insert our menu into the menubar programatically, since we can't edit the nib file of the target application.

When you create a Cocoa application in XCode, XCode automatically creates your first nib file in your project for you. But since we created a bundle, our project doesnt have any nibs yet.

2a) In XCode 3, go to File->New File and make a new Cocoa nib file. Be sure to create an "empty nib" file not an "application nib" file. Just call it "Menu.nib". Doubleclick the nib file in XCode to open it in Interface Builder.

2b) In XCode 2, you can't create an empty nib from within XCode. Open Interface Builder directly, and choose an empty Cocoa nib as your starting point.



After building the menu (step 3 below) save your new nib file in your project directory as "Menu.nib". It will ask you if it should add the nib to the project, and if it should add the nib to the target "George". Answer yes to both.

3) Drag an NSMenu from the IB palette to the IB main window (the window that contains "File's Owner" and "First Responder".) Then (XCode 3 only) doubleclick it to create an editable menu on your screen. Delete all but one menu item, and rename that one to "About..." Save your work then switch back to XCode.

4) Time to create the class which will contain our menu callback and, in this case, also bootstrap our plugin. Create a new Objective-C class file called "GeorgeController". Be sure to also create the header file. Since we're going to add the menu programatically, we need to create a variable that will be our handle on the menu. That goes in GeorgeController.h:

@interface GeorgeController : NSObject {
    IBOutlet NSMenu* topMenu;
}

@end

Now in GeorgeController.m, add a typical dealloc method, and an almost typical init method:

#import "GeorgeController.h"

@implementation GeorgeController

- (id) init {
    self = [super init];
    if (! self)
        return nil;

    [NSBundle loadNibNamed: @"Menu.nib" owner: self];
    return self;
}

- (void) dealloc {
    [super dealloc];
}

@end

In the init method, we're loading the nib file we just created.

One more thing before we switch back to Interface Builder - let's write the callback that we're going to attach to the "About..." item:

- (IBAction) orderFrontAboutPanel: (id) sender {
    NSImage* icon = [[NSWorkspace sharedWorkspace] iconForFileType: @"bundle"];

    [icon setSize: NSMakeSize(128, 128)];
    NSDictionary* options;
    options = [NSDictionary dictionaryWithObjectsAndKeys:
        @"George", @"ApplicationName",
        icon, @"ApplicationIcon",
        @"0.01", @"Version",
        @"", @"ApplicationVersion",
        @"Copyright (c) 2008 __MyCompanyName__", @"Copyright",
        nil];
    [NSApp orderFrontStandardAboutPanelWithOptions: options];
}

and add the method signature to the .h file:

@interface GeorgeController : NSObject {
    IBOutlet NSMenu* topMenu;
}

- (IBAction) orderFrontAboutPanel: (id) sender;

@end

Make sure to save both files so IB can see your changes.

One thing to note here is that our plugin can access the NSApp instance of our target application - here we call on NSApp to create an About popup window. A little further down we'll use NSApp to get at the application's menubar. Your plugin will probably call methods of NSApplication to initiate most of its interactions with the target app.

5a) Now go back to Interface Builder. If you're using IB3, click on "File's Owner", then open the Inspector and go to the Identity pane. Under "Class Identity" select GeorgeController. If GeorgeController is not listed, you may need to go to File->Read Class Files... and navigate to GeorgeController.h in your project, then try again.

Now that IB knows that GeorgeController is the File's Owner, you should be able to choose Connections from the Inspector and see topMenu under Outlets and orderFrontAboutPanel: under Received Actions. Drag from topMenu to the titlebar of your IB menu under development, and then drag from orderFrontAboutPanel: to the About... menu item.



Here's a gotcha: if your header file contains syntax errors, IB won't be able to parse it but also won't tell you that it can't. Like a naughty puppy, it will just quietly not do what you want it to do. If IB seems to be defying you, try compiling your project and see if you have any syntax errors.

5b) If you have Interface Builder 2, click on "File's Owner" and then choose Custom Class from the Inspector. If GeorgeController is not listed as an option, you need to actually drag GeorgeController.h from XCode's main window to IB's main window. Set GeorgeController as the custom class for File's Owner. Now choose Connections from the Inspector, then Ctrl-drag from File's Owner to the titlebar of your IB menu under development, then click Connect in the Inspector.

Next Ctrl-drag from the About... menu item to the File's Owner object. If the Inspector says "No actions in GeorgeController", click on the Classes tab in the IB main window, then select "Read GeorgeController.h" from the Classes menu in the menubar, then try the Ctrl-drag again. When you see orderFrontAboutPanel: in the Inspector, hit Connect. XCode 3 may be looking pretty attractive by now. :)



6) Next we're going to use the awakeFromNib method as our opportunity to attach our menu to the target application's menubar. You don't call awakeFromNib from your code - when a nib is loaded, awakeFromNib is automatically called on the instance of the class associated with File's Owner. So define an awakeFromNib like this:

- (void) awakeFromNib {
    NSMenuItem* item;

    item = [[NSMenuItem alloc] init];
    [item setSubmenu: topMenu];

    [topMenu setTitle: @"George"];

    [[NSApp mainMenu] addItem: item];
    [item release];
}

Note that we finally use the IBOutlet topMenu, this one moment is the whole purpose of his existence. Also, we take advantage of NSApp again to give us the mainMenu of the target application so we can manipulate it.

7) Now we're ready to do the work specific to SIMBL. Our class needs to define a class method - not an instance method! - called load which takes no arguments and returns void. SIMBL always looks for a load method in the NSPrincipalClass (we'll get to that) and that's where we bootstrap our plugin. In our case, there's not much to do.

+ (void) load {
    [[self alloc] init];
}

When inside a class method, "self" does not represent an instance, it represents the class itself. So we alloc and init a new controller instance, our init calls loadNibNamed:, which causes awakeFromNib to be called, which causes our menu to be attached to the menubar, which worries the cat that killed the rat that ate the grain that sat in the house that Jack built.

8) Finally, we edit the Info.plist. Before it calls our load method, SIMBL looks in the Info.plist of our bundle to see (a) which application(s) we want to plug into and (2) which class in our bundle contains the load method that we want SIMBL to call. In this example our bundle has only one class, but in a real project you'll have more.

This next part is straight from the CULater wiki.

If you just doubleclick the Info.plist in XCode, it opens in the XCode editor. If you'd rather, you can rightclick and Open With Finder, which will bring up the Property List Editor. Either way, set the NSPrincipalClass to "GeorgeController", and then create a new array key called SIMBLTargetApplications. Since this is an array, you can create multiple entries and have your plugin be loaded into multiple applications. In our example, we're just going to have one array entry, telling SIMBL that George should be loaded into Apple Mail:

    <key>NSPrincipalClass</key>
    <string>GeorgeController</string>
    <key>SIMBLTargetApplications</key>
    <array>
        <dict>
            <key>BundleIdentifier</key>
            <string>com.apple.mail</string>
            <key>MaxBundleVersion</key>
            <string>*</string>
            <key>MinBundleVersion</key>
            <string>*</string>
        </dict>
    </array>

How to know the BundleIdentifier and BundleVersion? Don't try to guess at it, cause they're not necessarily related to the application's name or displayed version. You need to manually inspect the application's own Info.plist. Go to the application in the Finder, right-click, and choose "Show Packge Contents". Go into the Contents folder and open the Info.plist that you find there. Look for the CFBundleIdentifier (not CFBundleName!) and CFBundleVersion (not CFBundleShortVersionString!) Although, SIMBL will allow you to use wildcards in the versions as you can see in our example.

9) You're ready to build. Hit Build and deal with any errors. If you've followed the example code exactly it shouldn't generate any warnings either. Right-click the George.bundle in the XCode main window, select Reveal in Finder, and drag the bundle to your ~/Library/Application Support/SIMBL/Plugins directory. Start your target application (Apple Mail if you followed the example Info.plist) and you should see a "George" submenu in the menubar. Congratulations! You're ready to build out our example into a plugin that actually does, you know, stuff.

Credits:
Mike Solomon for the Cocoa Reverse Engineering page on the CULater wiki.
I figured out most of what wasn't in the wiki by picking apart the source code to GreaseKit. GreaseKit is Copyright (c) 2007 KATO Kazuyoshi.

Sunday, February 17, 2008

As a Baby Gnome He Had So Much Potential


(Originally written April 9, 2004)

Conceptual integrity in turn dictates that the design must proceed from one mind, or from a very small number of agreeing resonant minds.

- Frederick Brooks, The Mythical Man-Month


Date: Thu, 8 Apr 2004 13:47:53 -0500 (EST)
From: Adrian Hosey
To: Thomas R. Hall
Subject: Re: good article

On Thu, 8 Apr 2004, Thomas R. Hall wrote:
: On Thu, 8 Apr 2004, Adrian Hosey wrote:
: >
: > http://mozillazine.org/articles/article4584.html
:
: That is a good article. How do you feel about the role that Mono may play
: in all of this?

I despair of any of it coming together at all. cf. our conversation yesterday about GNOME's failure to produce a scriptable desktop.[1] Granted that hasn't even been a stated goal of GNOME for years, but I hold it up as an example of the amount of energy lost from lack of unified vision and the amount of time spent dorking around with the core components and less time making stuff people can _use._ See also here

http://cinepaint.bigasterisk.com/WhyMigrateFromGTKToFLTK

I think the desktop battle has been lost for at least one more generation. i.e. FOSS guys aren't going to get their shit together in time to defray the market dominance of Longhorn. However I also don't believe that Longhorn will somehow signal the end of the contest the way Brendan Eich thinks it will. The current set of tensions will continue on into another round.

If someone is going to unhorse Microsoft on the desktop I don't think it will be FOSS as we know it. It may be Apple (wishful thinking) it may be Novell/Ximian - in which case Mono would play a big part in the new architecture. It may be someone we haven't met yet. I don't think there's room to bring a whole new closed-source OS to the marketplace right now (witness BeOS) but there's no reason someone can't deliver a quality desktop OS on a Linux kernel, if the project is executed with some focus. Ironically this may put Novell in the best position. They should break with GNOME and just do their own thing the way they think it should be done. Do it open source, do it closed source, but do it with hierarchical leadership and not design by committee.


If you're thinking that Mono will bring home the promise of write once run anywhere between Linux and Microsoft platforms, I don't think that's going to fly. Microsoft will make sure it doesn't. Look at SMB. Look at W2k's broken Kerberos. Look at the history of Java. Look at per-processor licensing. It is not the nature of that corporation under its current leadership to play on an open field.


 [1] This is alluding to an earlier conversation of which I don't have a transcript. But recently I've been learning about Apple Events, Applescript, and the ability of other scripting languages to substitute for Applescript via OSA. When I started using GNOME in 1998 I was drawn to it by a post from Miguel de Icaza that described GNOME's goal to be a scriptable, network-transparent desktop environment. I can't find a record of that post but there is this article. Skip down to the question "What differentiates Gnome from other window managers?" to read Miguel describing the vision he had for GNOME back in 1998.

I was really excited by his vision. He was describing a Linux desktop that would not just equal other desktop systems but leapfrog them in features and power. The closest thing on a Unix desktop might have been NeWS - and NeWS is long gone. It was going to have embeddable components and network transparency via CORBA. It was going to support bindings for multiple scripting languages which would drive the components via their CORBA interfaces. It was going to kick ass.


If you get on Google you can find posts I made to the GNOME mailing lists. You can find a few small patches I submitted, those patches now gone because their code bases have long since been rewritten. I was excited and I wanted to be involved in whatever small way I could. That was six years ago.

I'm bummed that in six years GNOME has not yet become a scriptable network-transparent desktop. What bums me the most is that it never will be. GNOME no longer even aspires to those things. Read contemporary editorials about the future of GNOME and you'll hear about things like HAL and DBUS and transparent windows via OpenGL. Nothing that other desktops aren't already doing. At the best one could say that GNOME aspires to be a best-of-breed hybrid from among the current desktop systems. At the worst one could accuse it of being a knock-off.

What happened? I'm not sure. My first thought was that GNOME needed a benevolent dictator and it never really had one. On the other hand there are many open source projects that run based on a "foundation" that do okay, like Apache. Is there some set of criteria that determines what kind of projects need a benevolent dictator?

Some of the language in that email is probably harsher than is fair, but I wanted to repost it verbatim because it conveys a certain amount of frustration. I'm sure most of the contributing programmers to GNOME have their shit together as people, because finding the time to do free software is hard. However if we speak of the GNOME project as if it were a person, I don't think the GNOME project has its shit together, citing the many fickle changes in direction over the years regarding various components. That's why I think the project could have benefited more from the benevolent dictator model. Or maybe the GNOME project does have its shit together and it just didn't put its shit in the basket where I wanted it to. I do feel confident that the GNOME that exists today is not the GNOME put forward in 1998.

Anyway that's why I suggested above that Novell "should break with GNOME and just do their own thing the way they think it should be done." I don't know if Miguel still has his sights set on the vision he described in 1998. If he does he should just do it and not worry anymore about playing nice with the other programmers.

Update 2006-02-08: I'm not saying anyone heard me in particular, but just check it out.