Tuesday, January 31, 2017

Adventures in hardware hacking: adding foot pedal support to emacs

A few months back I came across this video showing how to control a DSLR with a foot pedal. This re-ignited a long time project I've wanted to try: adding foot pedal control to emacs. How cool would it be to control emacs with not only 10 fingers, but your feet!

Looking more into this, I learned that such an idea was both totally doable and relatively inexpensive. Here's a recipe for exactly what I'd need to realize this dream: USB Foot Switch. The parts were easy enough to order off of Adafruit's website. I'd needed only a $7.00 foot switch and a $10.00 microcontroller.

The instructions promised a build time of less than an hour, however, I was skeptical. My area of expertise is in the software side of things, so clipping wires, soldering and such seems awfully intimidating.

To get around this, I decided to follow the instructions in reverse. First I'd program the chip. Then I'd see if I could get the keyboard behavior working. Then I'd plunge into the soldering side of things. It would take longer, but I'd get to start in my comfort zone.

I figured the best place to start was to get the hello world of programs running on the Trinket Pro chip, an LED blinker. To accomplish this, I followed the tutorial here.

Being an emacs snob, I completely skipped over the IDE instructions and jumped right into using AVRDude. Heck, a simple brew command line installed avrdude. I was on a roll. And then things came to a screeching halt when my attempts to use avrdude gave me various error messages. I googled for solutions, but it was beginning to look hopeless.

So, plan B: I returned to the IDE Instructions. While I had a couple of false starts, the instructions were really quite accurate. Everything seemed to work. I loaded up the blinker program:

int led = 1; // blink 'digital' pin 1 - AKA the built in red LED

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);

}

// the loop routine runs over and over again forever:
void loop() {
    digitalWrite(led, HIGH); 
    delay(1000);
    digitalWrite(led, LOW);
    delay(1000);
}

Hit the upload button, and poof, I successfully loaded the program into the chip. It was so effortless that I actually dug around and turned on verbose messages just to confirm that it was truly working.

There was only one problem: the program appeared to load, yet the LED on the board didn't blink. What the heck?!

On a whim, I brought up the pin-out of the Trinket Pro 5v:

Upon closer inspection I realized that the red LED I was after was labeled as #13. Aha! I updated my program to set 'led' to 13 and uploaded it to the device. And just like that, I had a blinking Trinket. Whoo!

OK, so I can get my code to run on the Trinket. Next up to was to do something with the keyboard library. I altered my blinking program to be like so:

#include <cmdline_defs.h>
#include <ProTrinketKeyboard.h>
#include <ProTrinketKeyboardC.h>
#include <usbconfig.h>

const int PIN_SWITCH = 0;    // Trinket pin to connect to switch 
int led = 13;

void setup()
{
  pinMode(led, OUTPUT);
  TrinketKeyboard.begin();  // initialize keyboard library
}

void loop()
{
  TrinketKeyboard.poll();
  digitalWrite(led, HIGH);
  TrinketKeyboard.print("Hello World!"); // use for string instead of char
  delay(5000);
  digitalWrite(led, LOW);
  delay(3000);
} 

Along with toggling the LED, it spits out the text Hello World every 8 seconds.

I pushed the program the Trinket, and lo-and-behold, it worked! Now, it was a particularly useful program, but it did show that I was able to get the Trinket to simulate a keyboard action.

The final task I wanted to try was to have the board generate Hello World in response to a circuit being closed. After all, the switch is just a fancy way of grounding pin #1. At this point, any hardware geek would have busted out their soldering iron and gotten to work on finishing this project. But I wasn't quite ready to go there. I grabbed a few short lengths of wire and crudely attached them to the board by winding them into place:

I then installed the following program on the Trinket:

#include <cmdline_defs.h>
#include <ProTrinketKeyboard.h>
#include <ProTrinketKeyboardC.h>
#include <usbconfig.h>

const int PIN_SWITCH = 0;    // Trinket pin to connect to switch 
const int LED        = 13;
void setup()
{
  // Set button pin as input with an internal pullup resistor
  // The button is active-low, they read LOW when they are not pressed
  pinMode(PIN_SWITCH, INPUT_PULLUP);

  TrinketKeyboard.begin();  // initialize keyboard library
}

void loop()
{
  TrinketKeyboard.poll();

  if (digitalRead(PIN_SWITCH) == LOW)  // If the foot switch grounds the pin
  {
    TrinketKeyboard.print("Hello World!"); // use for string instead of char
    digitalWrite(LED, HIGH); 
    delay(3000);
    digitalWrite(LED, LOW);
  }
}

With the Trinket plugged into my Mac via the USB cable, I ever so carefully touched the two exposed wires together. And to my amazement, Hello World blurted out on the screen. I tried again. And again. Success!

The end of the project is now in sight: I need to wire in the foot pedal switch and update the program to send a keystroke rather than a chunk of text. And then I need to figure out what bit of magic I want emacs to do when I hit the foot pedal. Hopefully that will be the hardest part.

Man, this Arduino hacking stuff is fun!

Update: While I had visions of using the pedal to power emacs, my first real use was a macOS hack.

Monday, January 30, 2017

Appioo African Bar and Grill

This past weekend we had a real treat: we got to try Ghanaian food for the first time. And best of all, we had friends of ours from Ghana to help lead the way. The restaurant was Appioo African Bar and Grill, and we all walked away quite satisfied.

While vegetarianism isn't a common practice in Ghana (or so we were told), the restaurant has an excellent selection of veggie choices. I tried a veggie version of Waakye, while Shira had Nkontombre (spinach stew). The kids also went for Waakye, though they included various meat selections.

Shira's dish was reminiscent of Ethiopian cuisine, whereas the rice and beans of Waakye corresponded more to Mexican food. I was surprised to see that the food was served without bread (such a universal staple in so many countries) but our Ghanaian friends weren't surprised by this, apparently that's par for the course.

My meal was tasty, but the fried plantains were by far the big winner. Though, who doesn't like fried bananas?

We're truly lucky to live in an area where you're only a 15 minute drive from a brand new cuisine. Our server couldn't have been more nice or more helpful. Next time you're looking for something a little different head over to Appioo and enjoy!

Weekly Discoveries: Bipolar Sunshine, Houndmouth and a Medicine Chant

A big thanks to friends for the Houndmouth recommendation. And how about that one armed trash bag technique?

Yesterday I had my 3rd Yoga class experience, which explains the 3 hours of Yoga music on the list. The class is absolutely fascinating and compares and contrasts well with my Krav experience. Either way, I'm back to feeling like an uncoordinated oaf. At least I'm getting some new musical inspiration from the class.

Enjoy!

Thursday, January 26, 2017

Packt Publishing's Free Book Of Day, Command Line Edition

My friend Nick recommended I check out Packt Publishing's free eBook of the day. To my surprise, the books offered there are the real deal. Today's book, for example, is Learn Penetration Testing with Python, a book I find interesting, but couldn't justify forking over actual cash for.

Now I suppose most people would simply bookmark the page and move on with their life. But for some reason, perhaps the technical nature of the site, I figured I could use this as a hacking opportunity (a hackertunity, if you will). Specifically, I wanted a command line tool that would echo to me the current free offer.

I give you: packtfree:

#!/bin/bash

##
## command line utility to retrieve the current free
## book from packt publishing
##

url=https://www.packtpub.com/packt/offers/free-learning

clip_url () {
  echo -n $url | clip
}

while getopts ":uc" opt ; do
  case $opt in
    u) echo $url ; exit ;;
    c) clip_url ;  exit ;;
    \?) echo "Usage: `basename $0` [-uc]" ; exit ;;
  esac
done

clip_url
curl -s $url | pup  '.dotd-main-book-summary h2, .dotd-main-book-summary div:nth-child(4)'

And running it produces:

$ packtfree
<h2>
 Learning Penetration Testing with Python
</h2>
<div>
 Utilize Python scripting to execute effective and efficient penetration tests
</div>
$ packtfree -u
https://www.packtpub.com/packt/offers/free-learning

One non-obvious feature is that running packtfree places the URL to the free book on the system clipboard. This makes it easy to switch to a browser of my choice and paste it if I want to redeem said book.

While this is a pretty obvious example of over-engineering a problem, it does address a challenge that I really haven't had a good answer to. What's the best way to work with HTML data on the command line? For text, you've got sed, awk and countless other old school Unix utilities. For json, the clear winner is jq. But what about HTML / XML?

In the past, I've used tools like w3m to convert HTML to text and hack away from there. But that's always a compromise.

Doing a fresh search for this challenge turned up pup, which is a fantastic command line tool for extracting and manipulating HTML. It's exactly what I was searching for, and unlike some XML tools, had no problem working with the real-life content over at packtpub.com. Nearly the entire script above is setup, with the main functionality being this sweet one-liner:

curl -s $url | pup  '.dotd-main-book-summary h2, .dotd-main-book-summary div:nth-child(4)'

pup is definitely going in my command line toolbox.

Look at that, Packt books are already teaching me something, and I didn't even have to read one.

Wednesday, January 25, 2017

A Most Satisfying Treat

There's something profoundly satisfying about cracking open a fresh coconut. Maybe it's the fact that step one involves going into the basement to retrieve a hammer and awl (and step two involves dousing said tools in Everclear to make them at least somewhat sanitary). Or maybe it's that solid cracking sound. Either way, today's treat was both fun to open and tasty to eat.

The jury is still out on my taste for coconut water. On one hand, the slightly sweet taste is good, and the fact that it has health benefits is a nice bonus. On the other hand, it does taste like warm dishwater, so there's that.

I recall my first run-in with coconut water. It was in Punta Cana, 7 years ago. I was psyched that I scored a coconuts with a straw being offered by the resort. I took a big 'ol pull, only to be met with an off-tasting liquid. I recall thinking: surely people don't actually drink this stuff, do they? I've grown since then.

It seems like everyone has their own hack for accessing coconuts. Ultimately, it was these two videos that got me access to the one above. The first video is quite exhaustive, while the second video is a quicker and easier method. Either way, the results are more or less the same.

Happy Cracking!

Tuesday, January 24, 2017

Review: Show Your Work

I learned about Show Your Work from a bag dump of all places. Fortunately, the library had it, so it was easy enough to get my hands on.

For reasons I can't fully explain, I started off reading the book with a bit of a negative attitude. Who was this Ausin Kleon character to dispense pithy bits of wisdom; every chunk he offered up I mentally tried to find fault in.

And then at around page 40 it hit me: nearly every bit of advice he was offering was something I was already doing and fully believed in. Have a place on the web that's not connected with a social network? Check. Share your discoveries with others, instead of hording them. Check. Read obituaries. Check (see rule #1). Share every day. Check. And so on. It's almost scary how much of Kleon's advice I can get behind. In a number of cases, he gave voice to a habit or behavior I intuitively knew was a good thing but hadn't found the words to express.

For someone like myself who attempts to blog every work day, Share Like An Artist is both confirmational as well as inspirational. I already know the value of publishing every day, but with Share Like An Artist I have a few fresh approaches I can take to finding meaningful content. He also provides solid guidelines to help you keep from publishing junk, something that's awfully easy to do on the web. I found the diagram on page 124 to sum up the challenge well:

(Under Sharing - Hoarder)
(Smart Sharing - Contributor)
(Over Sharing - Spammer)

Perhaps it was the short, anecdote filled nature of the book that started me off on the negative path. This sort of book is intentionally short and readable, but that can also come off as flimsy and disposable.

In many respects, it reminds me of a discussion I had about The Dip a number of years ago. I found that tiny volume to be nearly life-altering. Yet the person I was chatting with dismissed it as nothing more than an obvious, lengthy, blog post. Quick books like these may be easy to dismiss, but in both The Dip and Share Like an Artist, there really is quite a bit of value packed into relatively few pages.

Both books are worth reading and re-reading.

Now, go share something.

Monday, January 23, 2017

A Hacker's Guide To The Dip

While searching around for an old post, I discovered this draft I originally composed on 6/26/2007. Nearly everything written below I still agree with. I can't imagine why I didn't publish this back in 2007.


Seth Godin's latest book, The Dip is a must read. In it, he describes the typical flow of a project: things start off fun and exciting, and overtime, become difficult. The fun goes away and all that's left is the work. This stage of the project is called The Dip. Seth's book explores this concept and makes valuable recommendations for dealing with it.

It occurred to me that most large programming projects fit this model to a tee. If you take on a complicated enough project, whether it's for business or pleasure, you're going to hit a stage where it's simply not much fun anymore. Here are some of my preferred ways to deal with this fact of life. I'd love to add yours to the list, so mention them in the comments. Thanks!

A Hacker's Guide To the Dip

  • Expect It. This is a key point in Seth's book: projects are going to have a dip associated with them, so decide up front if you are ready to go through it or not. If not, quit while your ahead.
  • Embrace It. This was a fairly surprising idea that Seth presented: The Dip is actually a very good thing. Long story short, it will keep out all your competitors. If you are struggling like heck to get a cross platform UI to work, then, the good news is, so are your competitors. Pick a tricky problem and nail it. If you've got a big dip, then you've got plenty of protection against other hackers.
  • Have Fun. I admit it, I like to program in Scheme because it's fun. That's not the only reason I do it, but when facing the fact that I've got plenty of hours of programming ahead of me, it's nice to start off with something that's just plain fun. Chuck Moore, the inventor of Forth has even advocated a fun-down approach to programming (see chapter 3 of Thinking Forth).
  • Little Victories. Rather than facing the huge goal of a finished and shipped project, setup small victories that you can accomplish. These may be things as simple as saying: I'm getting Tomcat setup today. Fight The Dip by making itty bitty dips that you regularly beat.
  • Write Only Mode. What to do when you're working on some tricky aspect of a project and more requirements start flooding in? This only makes The Dip larger. For this, I typically create a Wiki and start documenting each request. I don't think, evaluate or prioritize them - I just record them and continue on with what I'm working on. When I've gotten to my next victory and am ready to face more of The Dip, I can go back to the list.
  • Demand Flow. Flow is that intoxicating state where time seemingly stops and work just gets done. It's glorious. The Dip disappears during flow. Make sure you setup an environment which is conducive to flow - this means no interruptions and a long stretch of time program. Not every programming session can be like this, but you should plan for a significant number of these.
  • Tune Your Dev Environment. No serious project can get accomplished until the environment is there - this means a trivial code-compile-test loop, source control, deployment is automated, and lots of other details. If there's any roadblocks to basic development, The Dip will get you big time.
  • Share Your War Stories. When the going gets tough, and it will, it's handy to have a community you can share your frustrations with. By sharing your progress and getting ideas, you'll be reminded why the heck you started this crazy project in the first place. It will help you get and stay motivated.
  • Sharpen Your Tools. When The Dip starts really getting steep consider taking a step back and optimizing your tools. Sure, you've got the basic environment there, but are there things you're doing with 4 key strokes you could do with one? Are you adding the same header to every file, so why not get your editor to do that work? An improvement in your tool set will help increase your momentum and take away yet another roadblock to your success.
  • Program Yourself Out Of It. When things get arduous ask yourself why that is? Perhaps you're writing class after class which looks more or less the same. Time to break out the code generation tools. By taking a step back and looking at the problems behind your problems, you can have a big impact on making the development fun again.
  • Visualize A Solution. When I have a bit of free time, I'll often work visualize in my head how I'm going to program a solution to some problem. I'll run through in my head which classes I'm going to create and how they'll connect. If I'm lucky, I'll actually discover a problem and re-work the design before I even write a line of code. By having the solution in your head before you even sit down, the programming session goes much smoother and The Dip is held at bay.
  • Don't Force It. I can recall my first big project I worked on. I needed to free up some memory, so I traced through hundreds of lines of code adding in various calls to free(). It felt wrong at the time, but I just pushed through it. The next I realized I could have changed a single point in the source code instead of the hundreds that I did by moving up the stack one level. I learned my lesson - if it doesn't feel right, don't do it. The solution will come to you, don't rush it.
  • Eat. Sleep. I find that my outlook on life is dramatically effected by the amount of sleep I've had and my blood sugar. Every night of bad sleep I have in a row makes my outlook on life just a bit dimmer. And then, when I finally do get a good night sleep, poof, everything is great again. Know your body, and know when you should and shouldn't be doing battle against The Dip.
  • Run. For me, there's nothing that clears my head like a jog. I find that I can work out problems while huffing and puffing that stumped me in front of a computer. This works for me, find out what activities work for you, and give your brain a chance to process.
  • Stick With It. There's no secret to this - just keep programming like mad. And before you know it, you'll be through The Dip and onto the good stuff.

Friday, January 20, 2017

Weekly Discoveries: The Giving Tree, a bit of jazz and a most remarkable violinist

This week in music I stumble on a bit of jazz, and a remarkable guitar street performance. But the most unusual find has to be Lindsey Stirling's work.

Lindsey's video that I linked to has 4 million views - so it's not as if she's an undiscovered talent, it's just that I've been apparently living under a rock.

Lindsey's schtick is that she's a violinist. And dancer. And singer. And, she chooses to use her talents to make the most non-traditional violin, dancing and singing work possible. I can't say that I love her music, but I do love what she's accomplished. I can just imagine her telling her friends, yeah, I'm going to be a rock star solo violinist. Sure you are Lindsey, sure you. And that's what she is. Good for her!

In the non-music videos, there's a sweet bottle opener build, and proof that you can cook over tea light candles.

Happy inauguration day! When you've had enough political coverage, give these tunes a listen!

Thursday, January 19, 2017

Web Based Protocol Handlers: The fastest user experience in the shortest amount of dev time

Firefox's Web Based Protocol Handler support has to be one of its hidden gems. Once a handler is registered, you gain especially quick access to specific content on the web. For 3shrink, this means quick access to the URL behind the 3 letter codes the site produces.

For example, to visit the URL behind the code PQW I need only type the following in my browser bar:

  grow:pqw

The protocol handler serves as the complement to the 1 click bookmarket, insuring that both growing and shrinking a URL is as streamlined as can be.

Best of all, implementing a Web Based handler couldn't be simpler. You need only make a call to: navigator.registerProtocolHandler. Here's the code that powers the register protocol handler link on 3shrink:

<?php
/*
 * A PHP file for rendering the code needed to install a web
 * based protocol handler.
 */
$domain = item_domain();
$code = <<<EOF
(function() {
  var proto = prompt("Protocol? (ex: grow)");
  navigator.registerProtocolHandler(proto, 
                                    "http://$domain/grow?i=%s",
                                    "$domain Handler");
})()
EOF;

$code = esc_attr($code);
?>
<a href="javascript:<?= $code ?>">Install Firefox Protocol Handler</a>
(<a href="https://developer.mozilla.org/en-US/docs/Web-based_protocol_handlers">Huh?</a>)

Note how the code above prompts the user to enter the protocol name. That way, you could have personal and business and point them to two different 3shrink domains.

When you click to register a protocol, you'll a sort of pop-up bar message thingy:

The first time you use the protocol (that is, enter: grow:...) you'll be prompted to select the handler. While you're at it, select the checkbox so you're not nagged about this again:

One gotcha: attempting to access a protocol containing only numbers fails. For example:

  grow:347

Will take you to: http://grow.com:347/ - not what I wanted. The fix for 3shrink is to use any letter prefix. So you can type:

  grow:x347

I'm amazed I haven't found more uses for web based protocol handlers. For advanced users, you can offer a terrifically fast user experience at almost no cost in terms of development time.

Wednesday, January 18, 2017

Adventures in Landscaping

For the past few years, as I've hired someone to cut our lawn, I've wondered aloud to Shira what it would take to have some "real" landscaping done to our property. What if we brought in a pro, instead of just hiring someone to cut the grass. This year, I finally got off my butt and decided I'd try to get this answer. My first stop: I searched for landscapers on Yelp and sent out a few contact-us requests.

And this is where I learned my first lesson: there's a difference between someone who does landscaping and a landscape designer. Despite what I thought was a clear request ("hey, can you draw me up a design / recommendation for our property?") every person I contacted showed up ready to give me a price to cut my grass. Yeah, that's not really what I wanted.

Just as I was beginning to think that my request only made sense if you owned a 7 acre estate, I got a lead to an individual over at Merrifield Garden Center. I poured out my story about our tiny lawn, and how I'd been curious about what a professional would do to improve the property. Turns out, I was talking to the right gal. She explained to me that she'd be glad to assess our property, sketch up a plan, and of course, put together a proposal to get work done. There was a nominal fee to have her come out and write up the plans, which I was actually glad to hear about: I wanted to pay someone to think this through, versus showing up to my property and recommending their first impression.

A few weeks later, I had the plan in my hand I was so curious about. It called for quite a bit of hardscaping in the back yard, due to the amount of shade we get back there. It also had a clever selection of plants that would look good throughout the year, be low maintenance and be sized to our property. Heck, she even included an especially fragrant plant to cover an additional sense. In other words, she did things all the things a classic landscaper designer is supposed to do.

I now had my plan and a proposal to implement it. As I considered what to do with it, Shira stepped in and took control. Turns out, we could save a few bucks doing the work in the winter time. And before I knew it, we had a crew of folks demo'ing our front walkway. In the span of 2 weeks, I watched as our entire property was overhauled from blah to looking great.

I have to say, Merrifield did a top notch job. The designer who worked on the plans was at the site every day, tuning things as need be. Any concerns Shira or I had were quickly addressed. Overall, I couldn't be more impressed.

Want the name of our landscape designer? I'd love to provide it. Just hit me up at: contact.ben.simon@gmail.com and ask.

Tuesday, January 17, 2017

Gotcha of the Day: iOS Cordova App running in iSimulator Hangs on Splashscreen

I recently submitted Cordova iOS to iTunes, only to have it rejected because the app was hanging on startup. Turns out, the app worked fine on a real device, but on a simulator, the app's splashscreen wouldn't ever be hidden.

On the simulator, I did notice that if I returned 'home' and relaunched the app, it did function. It was all very confusing.

I was already working around this quirk in the splashscreen plugin:

The config.xml file's AutoHideSplashScreen setting must be false. To delay hiding the splash screen for two seconds, add a timer such as the following in the deviceready event handler

After further debugging, I learned that:

 navigator.splashscreen.hide();

was being called, yet, it wasn't having any effect.

To add insult to injury, the IO Simulator's console log wasn't showing the output of JavaScript's console.log(...) messages. The 'debugging' above involved using old school alert statements.

I solved the mystery of the missing console.log statements by following the instructions here. I'd totally forgotten that you can remotely monitor a web instance from within Safari. Frankly, it's sweet functionality, even if a bit non-intuitive.

  1. On your real iDevice or in iOS simulator go to Settings > Safari > Advanced and turn on Web Inspector.
  2. Desktop Safari: Safari > Preferences > Advanced and select the Show develop menu in menu bar checkbox.
  3. Now that you have either iOS Simulator open or your iDevice connected to your mac start using web inspector on your website: On your mac, open Safari and go to Develop

Along with my new found console.log statements, I saw the following message being repeatedly blurted out:

Refused to load gap://ready because it appears in neither the child-src directive nor the default-src directive of the Content Security Policy.

Eager to fix *something*, even if it's not the splashscreen bug, I Googled for this message. I found this discussion, which as the message itself suggests, shows that the Content-Security-Policy meta tag needs to be updated.

To shut the app up, I went ahead and updated my CSP to be:

<meta http-equiv="Content-Security-Policy" 
   content="default-src * 'self'; style-src * 'self' 'unsafe-inline'; script-src * 'self' 'unsafe-inline' 'unsafe-eval'">

I relaunched the app, and not only did the message disappear, but the call to navigator.splashscreen.hide() worked!

And of course, this all makes sense now: the call to hide the splashscreen was failing due to a lack of permission by the page. I added the permissions and things were back to working.

Why this was failing in the simulator and not a device, I can't really say. But I'm just glad it's working.

Monday, January 16, 2017

Weekly Discoveries: Israeli Rap, WOGN, Van Hacking and a Sweet Alchol Stove Design

So yeah, I meant to post this last Friday, but the week got away from me. Better late than never.

Among other finds: some sweet tunes, an remarkable van transformation and a novel stove design that requires little more than a tea candle, pop can and scissors to build.

Enjoy!

Friday, January 13, 2017

3shrink: adventures in one click shrinking

While I'm pleased with the core functionality of 3shrink, it definitely needed a faster interface. That is, a way to effortlessly encode and decode 3shrink URLs, as well as bits of text. For now I've only solved half the problem, the shrinking side of things. The solution was straightforward: create a couple of bookmarklets. Here they are in action.

With these bookmarklets in place, I'm only one click away from a 3shrink code.

The implementation of the bookmarklets is nearly trivial. Here's the PHP snippet that powers them both:

<?php
/*
 * A PHP file for generating a URL shrinking bookmarklet
 */
$code = <<<EOF
(function(){
  window.location = 
    'http://{$_SERVER['SERVER_NAME']}/shrink?i=' + encodeURIComponent($src)
})()
EOF;
$code = esc_attr($code);
?>
<a href="javascript:<?= $code ?>">3shrink <?= $label ?></a>

And here's the calling code that renders the bookmarklets in the footer:

<div class="footer">
  <a href="/">Home</a> | <a href="http://blogbyben.com/">Built By Ben</a> |
  Bookmarklets: 
  [<?= snippet('bookmarklet',
       ['label' => "URL", 'src' => 'window.location']) ?>]
  [<?= snippet('bookmarklet', 
       ['label' => "TXT", 'src' => "prompt('Text to Shrink')"]) ?>]
</div>

Installing the bookmarklets is as simple as browsing to your preferred instance of 3shrink, say http://projx.3shrink.com/ and dragging the appropriate links from the footer into your bookmark toolbar.

You'll want to note that the bookmarklet's a domain specific. That is, the bookmarklets in the footer of the site http://projx.3shrink.com register URLs with a different context than http://personal.3shrink.com.

With the shrinking side of things streamlined, the next area to focus on is how to quickly expand 3shrink codes. Stay tuned for that solution.

Wednesday, January 11, 2017

3shrink.com: In Pursuit of Paper Friendly URLs

But Why?

Recall that my paper based TODO list had two challenges to overcome. First, I needed a backup/archiving mechanism, second I needed an easy bridge from the paper to the digital world.

The former issue, backups, was easy solved: every morning I splay out the various index cards I'm tracking tasks on (with each card representing a single project) and snap a photo of them. If I lose, or corrupt any of the cards (read: spill tea all over them), I've got a photo I can use to recover my lost work.

The latter issue I solved by use of a jerry-rigged URL shortner. Essentially, I can turn any entity associated with a URL (an e-mail message, bug report, Google Doc) into a 3 letter code. I've found that even with my crappy handwriting I can manage to neatly print 3 characters.

Here's a card for a hypothetical Project X:

Using the URL Shortner, I can jump to the resource associated with the first task using the code CLW.

This all works, and works surprisingly well.

However, it was nagging at me that my URL Shortner solution depended on a now defunct Google Apps Lab Project. While this works for me now, nothing stops Google from pulling the plug on this project tomorrow and I'd be left hanging. Also, I figured if I was getting benefit from these super short URL codes, maybe other folks would, too.

Introducing 3Shrink

I therefore give you: 3shrink.com. 3shrink is yet another URL Shortner, but unlike others out there, it focuses on making extra short, text friendly URLs. (OK, it does feel a bit like 7 minute abs, but bear with me here.) I quickly coded 3shrink, and you're welcome to grab the source code here to play with it. Or, just head over to 3shrink.com to give it a try.

To make 3shrink truly useful I need to implement a corresponding bookmarklet and protocol handler. Stay tuned for those bits of code, but for now 3shrink does have some value.

For one thing, you can control the namespace associated with your 3 digit codes by changing the URL. The above codes, CLW, DEY and L48 were created in the projx.3shrink.com instance. So visiting http://projx.3shrink.com/DEY takes you to a valid link, whereas visiting http://ziggy.3shrink.com/DEY will most likely give you a 404 page. You can even point (CNAME) your own domain, say 3shrink.myawesomecompany.com, to the 3shrink server, and it will use that as the corresponding name space. It's all very mailinator like.

And like mailinator, there's effectively no security. There's no obvious way to get a list of all URLs associated with an instance, but if you spent enough time poking at projx.3shrink.com you could figure out all the URLs associated with it. So be smart. You can also use a more difficult to guess submdomain if you want a bit more security.

Throw &geek=1 on any 3shrink URL, and you gain access to a trivial text based API that Tasker, curl and friends can easily plug into. See:

$ little=`curl -G -s http://projx.3shrink.com/shrink \
  --data-urlencode i=http://someurl.com -d geek=1 `
$ big=`curl -G -s http://projx.3shrink.com/grow -d i=$little -d geek=1 `
$ echo $big
http://someurl.com
$ echo $little
HPT

And finally, 3shrink isn't limited to encoding and decoding URLs. It's happy to shrink any text that's 1024 characters or smaller. This lets you turn an address, latitude and longitude or a Tweet into a handy 3 character code. Like the following:

  RU5
  BMZ
  JK7

So there you have it, 3shrink.com. Go and put it to use. Where will your 3 letter URLs take you today?

Tuesday, January 10, 2017

My BestBuy Criticism Explained

A couple weeks back I stopped by Best Buy to purchase a new lens. The clerk who helped me was nice, but powerless: while the lens was on display, I wouldn't be able to actually try it out in the store. For one thing, all the lenses were locked to the camera bodies, and for another, all the camera batteries were dead. I told the clerk I would go ahead and buy the lens, but then I muttered something customer experience and talking to his manager.

I doubt the clerk understood what I was talking about, so here is my criticism, in detail.

For nearly everything I buy today, I first turn to Amazon. The prices are reasonable, the ordering process streamlined (1 click, baby!) and the customer service quite solid.

However, for certain expensive electronics, I've found a better option: buying them at the local Best Buy. Best Buy price matches Amazon without a fuss, and being able to pick up the item versus wait a few days is a nice bonus. Best Buy has a free rewards program that earns me back a few bucks on big purchase, which I can in turn, put forward toward my next tech splurge. Best of all is the return policy: I can easily walk into a Best Buy and return anything, versus having to repackage and potentially pay for shipping.

For something like a Canon EOS Lens, Best Buy was the ideal solution.

But the camera setup at Best Buy is clearly lacking. How on Earth can then expect to sell expensive tech like cameras without allowing folks to actually use them? That's like trying to sell TVs without allowing folks to turn them on. The obvious answer is, they don't really care about selling DSLRs. And that's OK; that's their prerogative.

However, Best Buy should know that it's earning my business because of their actions, and it's these same actions that can lose it. All it wouldn't take is for a photography obsessed clerk at Staples, two doors down, to go to his manager and push for a better photography setup. She would explain that not only should we have functioning cameras customers can experiment with, but we should have a short term rental program. Want to try out this $9,000 500mm lens? Sure, let us take your driver's license and here, have a good time. The process works for car dealers, so why can't it work for camera sales?

Oh sure, the process would take effort. And it may not earn a whole lot of new business. But I'd be there. And my days of stopping by Best Buy to pick something up would be over.

It's fine when a business choses not to offer a service or particular experience. Where things fail is in the blind spots: when a business doesn't even realize they're offering a cruddy service or experience. Choices are good, blind spots are bad.

Update: After hitting publish on this post, I figured I'd hit BestBuy.com and drop them a Contact Us message with this link. Unfortunately, I couldn't find any intelligent way to leave this feedback. Alas, this seems typical for a big company on web. Perhaps another blind spot?

Friday, January 06, 2017

Weekly Music Discoveries - New Year's Edition

These days, I find myself gravitating to YouTube for my music needs. In general, YouTube's on the fly 'mixes' are a serviceable way to pick a song and stream tunes, all with control of jumping to a totally different song when the mood strikes. The only issue I find after a few intensive days of doing this is that all my playlists tend to converge. While it blows my mind that a song can have *billions* of views (or listens?), and I'm willing to admit I enjoy popular music as much as the next guy, I did find myself craving something totally fresh.

At one point I tripped over Vevo's New Indie Videos and was pretty much blown away: for 70+ songs in a row I listened to band after band I'd never heard of. Sure not a lot of it was great, but just to 'discover' a new heap of songs and artists was a pleasure.

And so in that vein, I give you my weekly Music Discoveries for the very first week of 2017. Some of these songs struck me as winners the very first time I heard them, others took a couple of listenings before I realized I liked them. There's even a homage to the recorder if you play close enough attention!

So press play, sit back and enjoy.

Listen to Ben's Music Discoveries

Thursday, January 05, 2017

Review: The Time Traveler's Wife

I'm still wrapping my head around the Time Traveler's Wife by Audrey Niffenegger. But I can easily say this: I enjoyed the book. How else can I justify listening to 17 hours, 38 minutes and 14 seconds of audio!

SPOILERS BELOW. Proceed With Caution

Seriously, I really am having trouble explaining why I kept returning to this book over and over, until I listened to every last word. A big part of my appreciation for the book has to be how clever the author's approach was. Niffenegger has give us a cliche love story, yet with a single twist, she has managed to create something truly special.

I was skeptical for almost the entire book; surely Henry isn't *really* time traveling, there must be an alternative explanation. But no, this is the one aspect of reality Niffenegger asks us to suspend, and by doing so, the results are absolutely fascinating. That mundane love story becomes one of mystery and intrigue. And more than that, it shines a light on questions about the nature of time and disability that are ever present, yet usually obscured.

For Henry, life's moments are fundamentally bursty. One minute he's in the present, and the next he's flashed back dozens of years, having a new experience. I can't help but feel sorry for how temporary these moments are. We, on the other hand, are more fortunate: we live time in a steady stream, with our experiences happening in a neat and orderly fashion. Except, the older I get, the more I realize that it's us that's experiencing the illusion, not Henry. We feel that we can lock time in place: the perfect marriage, the perfect children, the perfect job and relax. But that's not how time works; ultimately our experiences will be bursty as well. We're no more able to make our experiences permanent than Henry is. Man that sounds depressing. It's not meant to be. It's just that I appreciate what Niffenegger has done: she's given us a character who seems so foreign, yet, he's each of us in many ways.

In many respects the book reminds me of the TV series Misfits, a raunchy British Comedy/Drama/Fantasy that pulls a similar trick to the book: each of the young characters in the show are mysteriously given a super power. Like Niffenegger, beyond this one bit of sci-fi, the writers have left the rest of the world unchanged. And like the book, we get to sit back and watch as characters navigate the normal world with their new abnormal ability. It's been years since I've watched Misfits (here, watch the first episode), but I remember being struck by how the same super power could be both an advantage and a disadvantage. And so it is with Henry's time traveling ability.

On the surface, being able to jump through time is an asset. What, with being able to pick winning lottery numbers or see glimpses into happier times to come. However, we quickly learn that time travel isn't all it's cracked up to be: it's dangerous, unpredictable and makes everyday activities like holding a job or being a spouse quite difficult. So which is it? Does Henry have a disability or a super power? I think the book, like Misfits, handles this topic well by answering with a resounding 'yes.'

We're quick to generalize circumstances: she's athletic, so she must be happy; he's only got one arm, so he must be unhappy. Books like the Time Traveler's Wife remind us that life is a lot more complicated, and that ultimately we have to play the cards we're dealt versus arguing about whether we were dealt good cards in the first place.

Gotcha of the Day: Generating and Using Custom Firefox Profile Icons on OS X

One feature I rely on are Firefox Profiles. Much of my life is spent in a browser, and profiles allow me to isolate work, play and development from each other. I can be logged into my work gmail in one profile, personal gmail in another profile, and be constantly clearing my cookies in a third.

With a little tweaking, the script I used to kick off different profiles on Linux was coaxed into working on OS X:

#!/bin/bash

case `uname` in
  Darwin)
    PROFILE_DIR="$HOME/Library/Application Support/Firefox/Profiles"
    FIREFOX=/Applications/Firefox.app/Contents/MacOS/firefox
    ;;
  *)
    PROFILE_DIR=$HOME/.mozilla/firefox
    FIREFOX=FIREFOX
    ;;
esac

##
## Kick off firefox in a specific profile
##
profile=$1 ; shift
dir=`(cd "$PROFILE_DIR" ; ls | grep -e "[.]$profile$")`

if [ -n "$dir" ] ; then
  $FIREFOX -new-instance -profile "$PROFILE_DIR/$dir"  2> /dev/null &
else
  echo Profile not found: [$profile]
fi

This solution has worked well, except for one detail: in the OS X dock all the instances had the same Firefox icon. The result is that to jump to a specific profile meant a game of guess-the-browser, often jumping me to the wrong instance. It was time to clean up this icon ambiguity.

Both MultiFox and this blog post present OS X solutions for running multiple instances of Firefox. Ultimately however, I decided to stick with my shell script above and use this plugin: OS X Dock Icon Changer to customize the Firfox icon in use.

With the Dock Icon Changer extension in place all that was left to do was to pick new icons for each of my profiles: Surf, Personal, Dev and DevJr. I'm sure I could have found some graphics on Google Images or the like, but for my purposes it was easier to use ImageMagick to generate these icons. And so here's a script to do just that:

#!/bin/bash

profiles='
 Surf:BigCaslonM:#26532B:#5ABCB9
 Dev:Herculanum:#826251:#F4ECD6
 DevJr:Chalkduster:#114B5F:#F3E9D2
 Personal:Krungthep:#101D42:#5ABCB9
'

for p in $profiles ; do
  name=`echo $p | cut -d: -f1`
  font=`echo $p | cut -d: -f2`
  bg=`echo $p | cut -d: -f3`
  fg=`echo $p | cut -d: -f4`

  echo $name
  convert -background "$bg" -fill "$fg" -font "$font" -size 512x512 \
          -gravity Center label:"$name\nFF" icons/$name.png

done

Each icon has its own font, foreground color and background color. You can run convert -list font to see all the fonts available. Here's the icons the script generated:


And here's the new Dock:

And just like that, the days of playing guess the browser instance are behind me. Whoo!

Tuesday, January 03, 2017

Gotcha of the Day: inserting the current timestamp on OS X

I'm settling into week #2 of life on a Mac and I decided it was time to address a feature I was sorely missing: the ability to insert the current timestamp via a simple keystroke. Consider this use-case: within ChangeLog's I'll include the timestamp a patch was released, and while typing 20 or so characters is hardly a deal breaker, it's clearly annoying to have hand enter the current date and time.

Surely OS-X had a simple solution for this challenge.

Initially, I was psyched to find this automator solution to the very same problem, but I never could get it to work. Finally, at the suggestion of this post I installed and configured TextExpander to do the job.

For the 10 minutes I've had this solution in place, I'm liking it. For one thing, TextExpander let's me write shell scripts, which means that I don't need to learn the macro language that comes with it. And for another, the system is text based rather than keyboard short-cut based. So I need only to remember to type 'TS' and the current date will appear, versus trying to recall if I need to hold down Windows + Alt + Shift + T to do the same job.

I don't yet have a sense of how TextExpander fits into the larger Mac ecosystem. Is it a utility I can trust and should pay for? Not quite sure yet. Sure, I miss my autohotkey, but I'm going to try to roll with this for now.

Monday, January 02, 2017

Greenhouse Photos From a Kid Perspective

During our last trip to Boston we toured the remarkable greenhouses at Wellesley College and I challenged the four kids to find and photograph their favorite plants. Other than having to show the kids exactly where the shutter button was on the camera, I gave them no other direction. Below are my picks for their best photos.

I absolutely love the pictures they took, and how fearlessly they jumped into the project. On top of that, they even shared the one camera with a remarkable degree of civility.

It's only a matter of time before I start having them guest write for the blog. Whoo!