Visor + Nocturne, the reverse SEP field

Every now and then I am presented by an irresistible challenge, and this is usually because it is ‘SEP’ (Someone Else’s Problem) which is invariably easier and more attractive than the problem that I myself have to solve. 

I’m a newbie when it comes to hacking on OSX plugins and whatnot, so this is the tale of a newb survival. Read on if you dare.

The setup

My coworker uses two great pieces of software called Visor and Nocturne, both created by Alcor before he went to work for Google. 

Nocturne is a great tool that will invert the screen colors to allow you to work at night without burning your retinas or waking your spouse via the bright screen next to them in bed.

Visor is a cool little SIMBL plugin that turns your Terminal.app into a Quake-style console.

The problem

Normally my coworker uses a black background with white text in his Visor terminal windows. But when using Nocturne this becomes the retina destroying white background with black text.

Step One: grab source code

Thankfully the source code for Nocturne and Visor are both available.

Nocturne, it turns out, uses an undocumented CoreGraphics call to do the screen inversion. This call, CGDisplaySetInvertedPolarity(flag) applies itself to a whole display at a time.

Visor, for its part, applies its own color profile to new terminal windows.

Step Two: formulate possible solutions

A couple of possible solutions immediately jump out.

  1. Implement a hot-key in Visor that will change the color profile to something that is white background with black text (such that Nocturne will invert this back to the white on black desired)
  2. Implement a hot-key in Visor that will invert just the Visor terminal’s colors using a CoreImage filter

The thing about an SEP is that the solution that I pick needs to be simple enough to do quickly (after all, it isn’t my problem) but difficult enough that I actually learn something.

So for arbitrary reasons I chose to implement solution #2.

Step 3: implementation

So it turns out that the day of this SEP I saw a post on twitter by @andy_matuschak about a SIMBL plugin he created called Grayifier that turns all non-active windows into a grayscale version of themselves.

The core goodness of this plugin is that it uses another undocumented CoreGraphics call to apply a CoreImage filter to just one window. So the (simplified and condensed) code to apply an inversion filter would look a bit like this:

    CGSWindowFilterRef inversionFilter;
    CGSConnection connection;

    CGSNewConnection(NULL, &connection);
    CGSNewCIFilterByName(connection, (CFStringRef)@"CIColorInvert", &inversionFilter);

    CGSAddWindowFilter(connection, [window_ windowNumber], inversionFilter, 1 << 2);

To remove the filter, the corresponding call is to CGSRemoveWindowFilter(conn, window, filter).

The last thing that needs to be done is to hook in a hot-key handler. I decided to use Opt-Esc.

    EventHotKeyRef invertHotKey = [dispatcher registerHotKey:53
                                    modifiers:NSAlternateKeyMask
                                       target:self
                                       action:@selector(toggleInversion)
                                  whenPressed:YES];

One snag was the fact that the Visor project contained a version of CGSPrivate.h that did not include some of the CoreGraphics functions used above. The easy fix was to replace it with the CGSPrivate.h from Grayifier.

Result: success

So, the end result is that with one extra hot-key, Visor can now invert its own window, which makes life with both Visor and Nocturne much happier. 

But more importantly, I found out a bit more about SIMBL plug-ins, some private CoreGraphics calls, and how some cool OSX utilities work.

UIWebView: things you can’t do

UIWebView is a really useful component, but there are a few things that it does not allow you to do easily (you can google for work-arounds to most of these, but they are often convoluted or imperfect):

  • you cannot easily prevent UIWebViews from scrolling horizontally, even if the content should fit
  • you cannot easily tell a UIWebView to scroll to a particular point (the workaround involves using javascript to scrollTo certain content)   (note: in iOS5 this may be fixed by the fact that you now have direct access to the scrollView of a UIWebView)
  • you cannot detect taps within a UIWebView - even a UITapGestureRecognizer attatched to a UIWebView is going to fail. (there are many ways to workaround this, including a custom UIWindow that traps all taps at a higher level and then passes them on, or javascript functions that you can add to the content that calls back to your application code)

Other interesting things to note:

  • every application has its own cookie store for UIWebView. So users who browse to a website within your app will not have the same cookies as when they use Safari itself
  • the shadows on the edges of a UIWebView (when you scroll past the page) can be hidden, and often looks best if you do (it involves accessing subviews of the UIWebView… finicky but not a private API)
  • UIWebViews can be made transparent. Set the document background color in your HTML to transparent and tell the UIWebView isOpaque:NO

Anyway, those are just some of my notes on UIWebView. It is a really powerful component, but sometimes doesn’t work the way I would expect.

If you have any corrections, feedback or questions, feel free to let me know (contact details in the bar on the right).

UITextField: placeholder text color

Why?

Because sometimes your UI needs something different from the 70% grey default.

How?

Subclass UITextField and implement:

- (void)drawPlaceholderInRect:(CGRect)rect {
     CGContextRef curRef = UIGraphicsGetCurrentContext();
     CGContextSetFillColorWithColor(curRef, [[UIColor blueColor] CGColor]);
     [self.placeholder drawInRect:rect withFont:self.font lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentLeft];
}