Archive for February, 2006

Proxi v0.13

Friday, February 24th, 2006

link: Proxi v0.13

In this update:

  • Fixed clipped text in screen message windows
  • Screen message windows can now be dragged (command-drag)
  • Screen message windows can now be made sticky (control-click)
  • Command-W shortcut works again
  • Added Open File task
  • Mysteriously missing PowerMate Light task no longer missing

Mostly just fixes for some annoying little problems that have sort of crept into the software recently. I did go ahead and add one new task while I was at it though, the Open File task. Handy for me personally to open a todo list that I’ve put together using Hotkeys, Screen Messages, a Timer and Clipboard extra value. I hope it’s useful to you.

In other news, I think we’re going to try and move this blog onto the Griffin site some time in the future. I’ll keep you posted on when / if that happens

Rolling your own Triggers and Tasks

Tuesday, February 21st, 2006

I’ve been asked about this a couple times. Yes, Proxi was designed from the beginning to support the addition of third party plugins, and it’s pretty straightforward to create your own. Here’s how it works:

At launch, Proxi loads every bundle in /Library/Application Support/Proxi/Plugins and checks each class in the bundle for conformity to the GTrigger or GTask protocols. Those classes that support one of these protocols are added to the component window and the appropriate menus.

The protocols are pretty straightforward

@protocol GTrigger
+ (GComponentDescription *) componentDescription;
+ (NSArray *) valueInfoArray;
- (NSView *) settingsView;
- (NSImage *) image;
- (void) setImage: (NSImage *) inImage;
@end

@protocol GTask
+ (GComponentDescription *) componentDescription;
- (NSView *) settingsView;
- (NSImage *) image;
- (void) setImage: (NSImage *) inImage;
- (BOOL) processNotification: (NSDictionary *) inValues;
@end

Notice both types of classes return a GComponentDescription object. This object is used to provide the user information about your component and also define the name of the class. Here’s an example of the speechTrigger’s componentDescription method:

+ (GComponentDescription *) componentDescription

+ (GComponentDescription *) componentDescription
{
static GComponentDescription		*sComponentDesc = nil;

if (sComponentDesc == nil) {
NSBundle	*ourBundle = [NSBundle bundleForClass: [speechTrigger class]];
NSString	*imagePath;

sComponentDesc = [[GComponentDescription alloc] init];
[sComponentDesc setComponentClass: @"speechTrigger"];
[sComponentDesc setCopyright: @"Griffin Technology Inc., 2006"];
[sComponentDesc setName: @"Speech Recognition"];
[sComponentDesc setOwner: @"Griffin Technology"];
[sComponentDesc setSummary: @"Use speech recognition to trigger on a given phrase."];
if (imagePath = [ourBundle pathForResource: @"speak" ofType: @"tiff"]) {
[sComponentDesc setImage: [[NSImage alloc] initWithContentsOfFile: imagePath]];
}
}

return sComponentDesc;
}

Pretty basic, so next let’s look at the other methods required by the GTrigger protocol:

GTrigger protocol

+ (NSArray *) valueInfoArray

Each trigger has to provide an NSArray of the values it will provide when it executes and also provide a description of each of those types in the form of an NSDictionary. The dictionary requires values for the GTriggerValueName key as well as the GTriggerValueType key. So you might define your array like this and return it from valueInfoArray:

sValueArray = [[NSArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys: @"Name", GTriggerValueName,
NSStringFromClass([NSString class]), GTriggerValueType, nil],
[NSDictionary dictionaryWithObjectsAndKeys: @"Count", GTriggerValueName,
NSStringFromClass([NSNumber class]), GTriggerValueType, nil],
[NSDictionary dictionaryWithObjectsAndKeys: @"URL", GTriggerValueName,
NSStringFromClass([NSURL class]), GTriggerValueType, nil],
[NSDictionary dictionaryWithObjectsAndKeys: @"Date", GTriggerValueName,
NSStringFromClass([NSDate class]), GTriggerValueType, nil],
[NSDictionary dictionaryWithObjectsAndKeys: @"Image", GTriggerValueName,
NSStringFromClass([NSImage class]), GTriggerValueType, nil], nil] retain];

- (NSView *) settingsView

You’ll most likely need to allow your users some means of configuring your trigger. They will do so through the view you return from this method. In reality you’ll probably only need to allocate a single view and return a pointer to it here. For example in most of the components I’ve written, I load my class’ bundle in the load method and store a pointer to a settings view and, as I use cocoa bindings in most cases, a pointer to a controller object for later use. For example, in speechTrigger’s load:

sTriggerNib = [[NSNib alloc] initWithNibNamed: @"speechTrigger" bundle: ourBundle];
if ([sTriggerNib instantiateNibWithOwner: NSApp topLevelObjects: &nibObjects]) {
NSEnumerator    *objEnumerator = [nibObjects objectEnumerator];
id                anObj;

[nibObjects retain];    // around for the life of the app
while (anObj = [objEnumerator nextObject]) {
if ([anObj isKindOfClass: [NSView class]])
sView = anObj;
if ([anObj isKindOfClass: [NSObjectController class]])
sController = anObj;
}
}

and later sView is returned from settingsView.

- (void) encodeWithCoder: (NSCoder *) inCoder / - (id) initWithCoder: (NSCoder *) inCoder

GTriggers should support standard keyed object encoding and decoding. Apple has plenty of information available.

- (NSImage *) image

Here again, you’ll probably only need the image from your trigger’s GComponentDescription, but you may want to alter it based on your GTrigger’s configuration. For example, the AirClick trigger adds a badge to it’s image based on the primary button. The NSImage returned from this method should have a height and width of 32 pixels.

- (void) setImage: (NSImage *) inImage

I’m not sure why this is here really :) Actually, just as a reminder that image needs to be KVO compliant. ie if you change your image, do so through [self setValue: image forKey: @"image"] or bracket the change with [self willChangeValueForKey: @"image"] and [self didChangeValueForKey: @"image"]

GTrigger informal protocol and notifications

GNotificationName

That’s basically it for a GTrigger… except you may want to, oh I dunno, generate a trigger. That’s pretty straightforward, post a notification named GNotificationName to the default NSNotificationCenter. You must supply a userinfo NSDictionary a key/value pair for every value you defined in valueInfoArray. For the valueInfoArray demonstrated above the notification would look like:

NSNotificationCenter    *defaultCenter = [NSNotificationCenter defaultCenter];

[defaultCenter postNotificationName: GNotificationName object: self
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
[self myName], @"Name",
[self myCount], @"Count",
[self myURL], @"URL",
[self myDate], @"Date",
[self myImage], @"Image",
nil]];

- (void) willBeHidden

Your GTrigger receives this message just prior to it’s settingsView being displayed. If you’re using cocoa bindings, this is a really good place to make sure your controller is bound to the correct model.

- (void) willBeShown

Your GTrigger receives this message just prior to having it’s settingsView removed from the view hierarchy. If your using cocoa bindings, this is a really good place to make sure you unbind your controller from your model.

GTask protocol

- (void) encodeWithCoder: (NSCoder *) inCoder / - (id) initWithCoder: (NSCoder *) inCoder

These serve the same purpose as they do for GTriggers.

- (NSView *) settingsView, - (NSImage *) image, - (void) setImage: (NSImage *) inImage

Same for these.

- (BOOL) processNotification: (NSDictionary *) inValues

Your GTask receives this message in response to a trigger. The keys in this dictionary correspond to the values provided by the controlling GTrigger’s notification userinfo dictionary concatenated with any Extra Values that the user may have defined for this trigger.

GTask informal protocol

- (void) willBeHidden / - (void) willBeShown

These serve the same purpose as they do for GTriggers.

- (void) setValueDescriptions: (NSArray *) inValueDescriptions

Implement setValueDescriptions if you’d like to keep tabs on the values your GTask can expect to receive from the GTrigger controlling it. Useful if you need to update some aspect of your UI or if you’re simply nosy.

Useful ProxiLib utilities and classes

GValueTextView

Make your NSTextView a GValueTextView in InterfaceBuilder if you want to embed trigger values in your text. Handles the displaying, copying, dragging and so forth of embedded trigger value tokens. Kind of like a NSTokenField but different.

+ (NSAttributedString *) substituteTokensInString: (NSAttributedString *) inString usingValues: (NSDictionary *) inValues

Use substituteTokensInString: usingValues: to substitute the tokens in an NSAttributedString with the values passed to processNotification. This method goes hand in hand with any GTask using a GValueTextView to accept user text.

The short version:

Create a Cocoa Bundle in XCode and link to the ProxiLib.framework. Create an object/s that conforms to GTrigger or GTask protocol. Compile, and place in /Library/Application Support/Proxi/Plugins. Restart Proxi.

Proxi v0.12

Tuesday, February 21st, 2006

link: Proxi v0.12

In this update:

  • Added Blueprint Browser to locate ready made blueprints
  • Proxi now auto saves your changes. Happy now Dustin!? ;)
  • Several crashies were located and fixed
  • Memory footprint substantially reduced
  • Blueprint file format changed (still compatible with old version) to a more compact form

The biggest change in this release is the addition of the Blueprint broswer:

The browser locates blueprint files online in our repository and you can add them to your trigger list as you would any other blueprint file. A description is available for each so you can get an idea exactly what commands are contained within the blueprint. If you have a collection of triggers and tasks, feel free to submit a copy to me and if it seems useful, I’ll add it to the repository.

Another pretty big change is the auto save function. No longer do you have to save your changes. This is handled automatically. Actually the entire file menu has been removed.

Yesterday I noticed that Proxi’s memory footprint was getting a bit out of hand, so I did a bit of sleuthing and located a couple of extremely wasteful areas. This reduced Proxi’s initiali memory footprint considerably (from about 40MB to 15MB).

That’s about it for this release. Next release, I’ll be looking (again) at the Screen Message task. It seems that sometimes the text is properly positioned within a Screen Message window and also, clicking a link activates Proxi. Hopefully both of these will be fixed next time around.

Proxi v0.11 (aka the I’m stupid release)

Friday, February 17th, 2006

link: Proxi v0.11

In this update:

  • fixed a problem where clicking the values window made it and the component window vanish
  • update Mail settings during install so the Mail trigger should work now
  • fixed a problem with loading on Intel based Macs

OK, so my quality control needs a little work. I let this mornings release go without realizing that clicking the values window would make it hide itself. That’s bad. Anyway, I fixed that and a couple other boo boos in this build. Sorry about that.

Proxi v0.10

Friday, February 17th, 2006

link: Proxi v0.10

In this update:

  • iChat task and trigger
  • Shell Script extra value
  • URL can now be embedded in other values or user defined text
  • View animation crash fixed
  • Trigger list disclosure states are now stored and recalled
  • Preliminary support for auto software update feature

Some good updates in this release I think. Most are pretty self explanatory, but I’ll go into detail on a couple of them.

First, there is a new extra value type: Shell Script (note: this is not an AppleScript). Use this extra value to append the results of a shell script to your trigger. To do this, open the extra values disclosure triangle for your trigger, click on the “+” button and select Shell Script.

In the value field, enter your script. The results will be placed in a value with the name indicated by the name field.

Another requested feature was the ability to embed a URL value in some other text or another value. I’ve added that in v0.10 as well. You’ll notice when you place an URL value in a text field, such as the Screen Message task, that a URL value has a popup triangle. Clicking on that triangle will present your choices for embedding that URL. You can select an existing value such as the title of an RSS feed item as I’m doing here:

Or you can select “Embed in text…” and enter whatever test your like, such as “link”.

That’s pretty much it for this update. Keep those cards and letters coming and feel free to spread the word about Proxi. We could use a few more users.

Thanks again for your help!

Proxi v0.09

Tuesday, February 14th, 2006

link: Proxi v0.09

In this update:

  • Repeating AirClick triggers
  • A bit more undo/redo capability
  • Speech Recognition trigger

A smallish update tonight: AirClick trigger can now be set to repeat. The repeat rate is fixed as it in the original AirClick software. This could be changed, but I figure the AirClick trigger settings view is already a bit more cluttered than I’d like.

I also threw a little speech recognition module together. It’s pretty self explanatory, just enter the phrase to trigger on. You may want to tailor the Speech Recognition settings in the Speech System Preferences Pane to your liking before using this.

It’s been a little quiet out there. I take requests, so if you have any let me know otherwise, I’m moving ahead with some work on URL handling and most likely an iChat (Adium too?) task for setting status and so forth.

Proxi v0.08

Friday, February 10th, 2006

link: Proxi v0.08

In this update:

  • Well, the name for starters
  • Background in Screen Message mimics desktop picture if applicable
  • Optionally prevent Screen Messages from opening a new window
  • Selecting a trigger clears task selections
  • Trigger / task list keyboard navigation (ala NetNewsWire)
  • Schedule trigger
  • iTunes control task

The first thing you’ll notice is that iNotify isn’t iNotify anymore. Proxi is the name now. I hope you like it, if not feel free to lie and tell me you do anyway.

Because the name changed, you’ll have a few items laying around that are no longer used, but before you remove these items you might want to make a copy of ~/Library/Application Support/iNotify/Store.xml. This file contains all of your settings and you’ll probably want to copy it into ~/Library/Application Support/Proxi/. After you’ve done this feel free to delete the following:

- /Applications/iNotify
- /Library/Frameworks/iNotifyLib.framework
- /Library/Mail/Bundles/iNotifyMail.mailbundle
- ~/Library/Application Support/iNotify

I added most of the items that people have requested over the last few days. But I still have a couple of requests that will have to wait until v0.09. In particular, AirClick repeating events and doing something with missing images. If you have anything else you’d like to see soon, just let me know.

One other thing I just remembered: triggers dragged to the Finder no longer use the .inpkg extension, but they are still valid files. I’m calling them Proxi Blueprint files now and the proper extension is .proxibp. Just change the extension and they should work just fine.

iNotify v0.07

Monday, February 6th, 2006

link: iNotify v0.07

In this update:

  • Additional undo support added
  • New artwork
  • Ability to run in UI element mode (no dock icon or menu bar)
  • Skype support. Both trigger and task
  • Saved state (visibility) of main window

The big addition is support for Skype. However, Skype’s framework is not yet a universal binary so the module does not live in iNotifyCore.bundle. If you’re running on an Intel Mac, you won’t have Skype support. Sorry.

Speaking of Skype, you can contact me on Skype at somegeekintn. That’s also my AIM screen name if you’d rather contact me that way.