Skip to content

Recent Articles

26
May

Compiling GPG 2.1 on OSX

I’ve had to compile the pre-release version of Gnupg a few times lately to test some changes.
While it’s not particularly difficult, it can be a bit persnickety to get right..
To that end, I thought I’d share the build script I’ve been using.


If it’s useful to anyone else, that’s awesome! At the very least, it’ll be easy for me to find it next time I want to play around again ;)

Please note – This isn’t necessarily the right way to do anything. I’m not a GPG dev. These changes worked for me.

Also note – As of 5/25/14, I needed to patch GPG to make it compile.
I’ve linked the patch as part of the steps below.
Hopefully there’s a better way that’s added to git-master soon ;)

#!/bin/bash
# GPG 2.1 Installation Notes.
# Run this in a temporary directory, such as ~/Documents/gpg-inst
# This will add gpg binaries inside a subdirectory - Ex:  ~/Documents/gpg-inst/gpg

# First, we'll want some prerequisites from Homebrew.
# If you don't have Homebrew installed, you can get it from brew.sh

brew update
brew install autoconf gettext pkg-config readline git wget


# Ideally, you won't have already installed libgpg-error, libgcrypt, libassuan, etc via homebrew.
# If you have, this technique should still work, since we're explicitly listing where to find them,
# but it's a bit more risky.

# The autodetected version of stdint doesn't work
export gl_cv_absolute_stdint_h=/usr/include/stdint.h
export LDFLAGS=-lresolv
export GETTEXT_PREFIX=/usr/local/opt/gettext/bin/


# Download the old version of automake.
# The current version of automake defaults to parallel tests, which won't work with gnupg.
# GnuPG maintainers have so-far declined patches to fix. 
# We can work around this by adding AM_INIT_AUTOMAKE=[serial_tests] to -every- lib...
# But it's easier to just install the old frickin version :/

wget http://ftp.gnu.org/gnu/automake/automake-1.11.6.tar.gz
tar -xzf automake-1.11.6.tar.gz
cd automake-1.11.6/
./configure
make
make install
AUTOMAKE_SUFFIX=1.11
cd ..


### Install the various prerequisite libraries that are part of gnupg.
## LibGPG-Error
git clone git://git.gnupg.org/libgpg-error.git
cd libgpg-error
./autogen.sh --force
./configure --enable-maintainer-mode --prefix=`pwd`/inst/
make
make install
cd ..

## Libgcrypt
git clone git://git.gnupg.org/libgcrypt.git
cd libgcrypt
# By default, this tries to run transfig for the docs.
# This isn't trivial to install for OSX, and I don't need these docs locally right now ;)
sed -i '' 's/doc //g' Makefile.am
./autogen.sh
./configure --enable-maintainer-mode --disable-asm --with-gpg-error-prefix=`pwd`/../libgpg-error/inst --prefix=`pwd`/inst/
make
make install
cd ..

## Libassuan
git clone git://git.gnupg.org/libassuan.git
cd libassuan
./autogen.sh
./configure --enable-maintainer-mode --with-gpg-error-prefix=`pwd`/../libgpg-error/inst --prefix=`pwd`/inst/
make
make install
cd ..

## Libksba
git clone git://git.gnupg.org/libksba.git
cd libksba/
./autogen.sh
./configure --enable-maintainer-mode --with-gpg-error-prefix=`pwd`/../libgpg-error/inst --prefix=`pwd`/inst/
make
make install
cd ..

## NPTH
git clone git://git.gnupg.org/npth.git
cd npth
./autogen.sh
automake --add-missing
./configure --enable-maintainer-mode --prefix=`pwd`/inst/
make
make install
cd ..


### Now, we can install GPG 2.1

git clone git://git.gnupg.org/gnupg.git
cd gnupg


# If GPG complains about "Undefined symbols for architecture x86_64:" you might need to apply a patch to the Makefile
# If so, you can use my hacked-together version until something better is released.
# wget https://gist.githubusercontent.com/e1ven/167af3ac8a196773fc46/raw/62f4deb4936a6a32634ac3ee12c9a06cb4c8eac7/makefile.patch
# git am --signoff < makefile.patch

./autogen.sh --force
./configure --sysconfdir=/etc --enable-maintainer-mode --enable-symcryptrun --enable-mailto --enable-gpgtar --with-readline=/usr/local/opt/readline --with-gpg-error-prefix=`pwd`/../libgpg-error/inst --with-libgcrypt-prefix=`pwd`/../libgcrypt/inst --with-libassuan-prefix=`pwd`/../libassuan/inst --with-ksba-prefix=`pwd`/../libksba/inst --with-npth-prefix=`pwd`/../npth/inst --enable-static=yes  --disable-endian-check --disable-dependency-tracking --disable-asm
make
make install
26
May

“This copy of the Install OS X Mavericks application can’t be verified”

I’ve been reinstalling OSX on a few old machines lately, and I’ve come across a rather odd message.

OSXInstallError
“This copy of the Install OS X Mavericks application can’t be verified”

After encountering this error, the OSX installer dutifully quits, leaving me scratching my head.
Since I was installing using a USB stick, at first I assumed the image or hardware were bad, and tried several variations on replacing drives and re-making the install image.

Eventually, I realized that the problem was particular to the destination system, rather than my USB stick.

One thing that can cause this error is if the time is off on the destination system.
Since the machines I was installing to had been sitting for quite some time, their batteries had depleted fully, and the clock had reset back to 2000.

Once I figured this out, and stopped wasting time on install media, this is pretty straightforward to fix.

On the Destination machine, once it’s booted into the install media – Go to the Utilities menu, and choose Terminal.

If you run the command

date

you can see the system time, and confirm it’s wildly incorrect.

To fix it, we just need to find the correct time/date from another system.
On another machine, fire up the Terminal, and run

TZ=US/Pacific date +%s

This will output the current unix timestamp in the PDT timezone. We want the PDT timezone, since that’s what OSX defaults to when the clock is reset.

Once we have this timestamp, we can enter it back into the Destination machine.
In the terminal, run

date -f %s TIMESTAMP

(Replacing TIMESTAMP with the timestamp from the last step, such as “date +f %s 1401065468″)

After this, close out of the terminal, and the install should proceed normally ;)

11
Dec

Running 1Password and Evernote under Seamonkey

I always find it fun to test new gadgets, and play with different ways of reaching the world.
I enjoy swapping phone platforms, OS, but mail clients and web-browsers are particularly susceptible to this, since they’re in that special fit of frequently used, but low cost-of switching.

Most recently, I’ve switched back to using a browser I used to love 10 years ago – Mozilla, or as it’s known now, Seamonkey [1]

Seamonkey logo

Seamonkey is the new name for what used to be called the Mozilla Application Suite, or, more commonly, Mozilla.
Like it’s predecessor Netscape 4, Seamonkey combines a Browser, Email client, and Newsreader into one svelt package. Even though the Mozilla Foundation quasi-abandoned Seamonkey back in 2005, volunteers around the world have kept it up to date.. Since it shares the engine with Firefox, it supports all major web features, and gives a really nice experience.

I had a lot of fun playing nostalgically with various Throbbers Ns ani, before getting down to the business of making it a mean, lean, modern web-browser.
Amusingly, with the resurgence of minimalism, the theme that was used during the beta looks perfectly at home.

Screen Shot 2013 12 10 at 6 01 44 AM

Modifying Extensions

As much fun as I was having, if I wanted to stay with Seamonkey for a primary browser, I couldn’t use it without extensions.
I’ve become far too accustomed to storing pages to read offline, using separate passwords for every site, and other things that aren’t built into any browser directly.

While quite a few extensions run out-of-the-box on Seamonkey, it’s often ignored compared to it’s vulpine cousin.
Luckily, they both use the same underlying engine, as well as the same UI framework, so porting them isn’t that bad.

Evernote

The first addon I ported over was the Evernote Web Clipper – It’s a cute little extension which lets me download webpages, and store them to read later.

Since it isn’t designed to run in Seamonkey, Mozilla (understandably) tries to discourage downloading the extension, but thankfully they do provide an override.

Download Extension

Once an extension is downloaded, it’s very straightforward to edit.
xpi extensions can be thought of as .zip files. [2]

By default, the files will unzip into your working directory, rather than a subdir (aka, a tarbomb)
This means that we should create a new subdirectory to work in

unzip evernote_web_clipper-*.xpi -d evernote-tmp/

The layout of modern extensions is pretty straightforward – The first file we care about is ‘install.rdf’
This file gives browsers the details of the extension – What browsers it’s compatible with, which languages it supports, etc.

Inside the file, there’s a section that describes it’s Firefox compatibility.

<!-- Firefox -->
<em:targetApplication>
  <Description>
    <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
    <em:minVersion>4.0b1>/em:minVersion>
    <em:maxVersion>24.0a1</em:maxVersion>
  </Description>
</em:targetApplication>

The section gives gives the Firefox ID, along with versions that are known to be compatible.
It’s straightforward to create a new entry just under it, and include that information for Seamonkey.

<!-- SeaMonkey -->
<em:targetApplication>
  <Description>
    <em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
    <em:minVersion>2.0</em:minVersion>
    <em:maxVersion&gt2.1.*</em:maxVersion>
  </Description>
</em:targetApplication>

That that that’s added, Seamonkey is willing to try to load the extension.
There’s one other change that needs to be made – It comes up fairly commonly when porting over extensions.

Firefox references the UI internally as ‘chrome://browser/content/browser.xul’, whereas Seamonkey uses ‘chrome://navigator/content/navigator.xul’

Thankfully, it’s easy to update this everywhere using some bash magic, without needing to manually chech each file.

    cd evernote-tmp
    for i in `find .`; do sed -i '' 's/chrome:\/\/browser\/content\/browser.xul/chrome:\/\/navigator\/content\/navigator.xul/g' $i; done > /dev/null 2>&1

Almost all extensions will need that fix. There are some other similar tweaks that other extensions might need, but it’s rare.
With that change in place, the extension should work fine in Seamonkey – It just needs to be turned back into an xpi file.

zip -r ~/Downloads/Evernote-for-Seamonkey.xpi *

Once this is an xpi file, it can be loaded like any other Extension that we downloaded from Mozilla.org.
Screen Shot 2013 12 10 at 6 24 59 AM
This adds the extension to Seamonkey, but by default doesn’t add it to the active toolbar.
It can be added by right-click the menu bar, choosing customize, and dragging the new button into the toolbar.

Screen Shot 2013 12 10 at 6 29 02 AM

Once that’s done, we’re good to go – The extension is in place, and we can properly save articles for reading later on.
Screen Shot 2013 12 10 at 6 30 05 AM

1Password

Another extension I rely heavily on is 1Password. It lets me generate unique passwords for each site I visit, so if they’re hacked, and my password is leaked, it can’t be used to break into my other accounts.
Converting it to work with Seamonkey is very similar to the procedure for Evernote –

Download the xpi file locally.

Download1Pass
Unzip into a tmp dir

unzip 1Password-4.0.1.xpi -d OnePass-tmp/

Edit install.rdf, and add the Seamonkey entry.

<!-- SeaMonkey -->
<em:targetApplication>
  <Description>
    <em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
    <em:minVersion>2.0</em:minVersion>
    <em:maxVersion&gt2.1.*</em:maxVersion>
  </Description>
</em:targetApplication>

Then change the browser.xul to navigator.xul, just like before.

    cd OnePass-tmp
    for i in `find .`; do sed -i '' 's/chrome:\/\/browser\/content\/browser.xul/chrome:\/\/navigator\/content\/navigator.xul/g' $i; done > /dev/null 2>&1

The 1Password extension has a few other places it’s looking for Firefox specifically that need to be tweaked.
It ships with a config file, ‘harness-options.json’, which sets various options.
One option, ‘is-sdk-bundled’ should be around line 4 – It chooses if the extension should use the version that’s built into firefox, or the one that ships with the plugin.

Setting this to true will ensure we load the version we’re about to modify.

 "is-sdk-bundled": true,

One of the files in the extension, ‘resources/addon-sdk/lib/sdk/loader/cuddlefish.js’ does another compatibility check.
Around line 50, there is a line that starts with ‘function incompatibility(module)’.
This can be bypassed in the terminal

sed -i '' 's/function incompatibility(module) {/function incompatibility(module) {return null;/g' resources/addon-sdk/lib/sdk/loader/cuddlefish.js

Once that’s fixed, it can be bundled up as an xpi, and installed via the gear icon in the admin menu, just like Evernote.

zip -r ~/Downloads/1Pass-for-Seamonkey.xpi *

While this loads, it’s not quite smooth sailing yet –

Screen Shot 2013 12 10 at 7 21 09 AM

In Version 4 of 1Password, Agilebits added a new security feature.
The app checks the gatekeeper signature of the browser that loads it, to ensure that it’s an approved browser.
The feature makes some sense – 1Password works by communicating with the browser over a socket, so adding some security to ensure we know who we’re talking to is reasonable.
Unfortunately, Seamonkey isn’t currently signed, so even if Agilebits wanted to, they couldn’t include it’s signature.

Of course, that’s no reason to give up ;)
I dug around in the app a bit – While I’m not really very talented at reverse-engineering apps, it looked to be fairly simple.
1Password runs in the background, listening on a socket, which the extension then connect to – When a connection comes in, 1Password looks up the Process that is talking to it.
This information is readily available in the system normally, such as with a lsof

2BUA8C4S2 34434 TCP localhost:10196->localhost:49738 (ESTABLISHED)
2BUA8C4S2 34434 TCP localhost:10191 (LISTEN)
2BUA8C4S2 34434 TCP localhost:10191 (LISTEN)
2BUA8C4S2 34434 TCP localhost:10196 (LISTEN)
2BUA8C4S2 34434 TCP localhost:10196 (LISTEN)

Once it has the PID, it can lookup the gatekeeper signature. Since all the major browsers are signed, it’s straightforward to compare against those signatures.

Screen Shot 2013 12 10 at 5 26 43 AM
Screen Shot 2013 12 10 at 5 32 09 AM

I can see where they’re going with this, and it’s an interesting idea, but the feature does make it a bit more difficult to use unsupported browsers.
Luckily, before I went too far down the rabbit hole of trying to patch the binary, I found an much easier way.
The new beta version has a bypass option built in.

Screen Shot 2013 12 10 at 7 35 49 AM

Success!
Seamonkey runs really well, and porting over a few plugins, even if you need to smash them around a bit, makes a great everyday experience.
I certainly wouldn’t recommend it for everyone, but it’s become my browser of choice — At least for this week.

Screen Shot 2013 12 10 at 5 41 06 AM

Footnotes

1 – I know that Mozilla prefers to refer to the suite as SeaMonkey, with both words capitalized. Personally, I find this a bit ugly, so I’ll be using the variant Seamonkey here.
2 – Technically, these are Jar files, but the distinction isn’t meaningful here.

3
Jul

Enable 1Password for Opera

I’m a big 1Password fan, and it’s one of the things which kept me from using Opera 12.15 and below.
The new Opera is based on Chromium however, so it’s sorta-kinda compatible with Chrome extensions ;)

1PassScreenshot

Note- I was able to install the extension using the instructions I’ve written up below.
I can’t guarantee it’ll work for you. It might very well corrupt your 1Password file, or do nasty things.
It will also be overwritten (and need to be run again) once Agilebits updates their Extension.
In Short – YMMV.

1Password extension for Opera 15

  1. First off, we’ll need to download the 1Password Chrome Plugin, from Agile Bit’s site
  2. Make sure you click the “Allow beta extensions” button.
    AllowBeta
    Then, click the download button next to “Google Chrome”.
  3. Opera will say *”Extension was disabled because it is from unknown source. Go to the Extension Manager to enable it.”*, click “OK”.
  4. ExtensionBlocked

  5. Go to Window, then select Extensions, to open the Extension Manager.
    ExtensionsMenu
  6. One there, the 1Password extension will be greyed out.
    Press Install.

    Screen Shot 2013 07 02 at 5 56 20 PM

  7. It will prompt you again in a little pop-up. Press install again.
    AllowInstall

    Make a note of the version- In the case of our screenshot, it’s such as 3.9.20.2
    Version
    If the plugin updates later, and this version has changed, you’ll need to re-run these steps.

  8. Close both the Opera Extensions window, and the Agilebits “Extension Setup” Tab.
    Go to File, and choose “Close Tab” for both tabs.
    CloseThis2
    If you do not do this step, when Opera re-opens it will re-open the Agilebits Extension installer , and re-install the default version of the extension.

    This would undo the changes we make below, so make sure you close this ;)
  9. Quit Opera. Make sure you go to the Opera menu, and choose Quit, even if no webpages are showing.

    Quit

  10. Open the Terminal (It’s in Applications, Utilities)
    This will allow us to perform the additional steps necessary to load the extension.
    You can also open the terminal through Spotlight if that’s easier ;)
  11. Move to the directory where your Opera Extensions are installed.
    Enter the following command into the Terminal, and press enter.

    cd "/Users/`whoami`/Library/Application Support/com.operasoftware.Opera/Extensions"
    
  12. Next we need to locate the 1Password extension we just installed.
    The exact name changes, so we want to look for it in the “Info.plist” file that is distributed with the Extension.
    The following “Find” command will search for it, then move to it’s directory.

    cd $(dirname $(ls -th `grep -r "AgileBits" --include "Info.plist" * -l` | head -n 1))
    
  13. Chrome has an “Omnibox” which combines the URL bar with Search.
    Opera has something similar, but doesn’t let extensions talk to it yet – So we need to remove this code from the Extension, so it can load in Opera.
    Run the two commands below in the Terminal.

    cat code/global.min.js | python -c "import sys,re;a = re.sub(r'chrome\.omnibox(.*?)a.message\)}','',sys.stdin.read(),flags=re.MULTILINE|re.DOTALL); print(a)" > tmp
    cp tmp code/global.min.js
    
  14. Re-open Opera.
    You should be all set at this point – The extension will need to sync, which might take a minute or two after clicking on the icon.
    You might also find you have luck opening or closing the window which loads the extension.

    Notes-

    I had initially thought that it would be a lot more complicated to port the extension, but Opera does not seem to have changed the internals as much as I thought.
    For instance, 1Password uses tests such as

    if("object"==typeof chrome) { alert("foo")}
    

    These work with “chrome”, not “opera”.
    The most complicated part is that Chrome 15 does not have the Omnibar property.

    I’m not an expert, but there appears to be a few other cases where the extension could be updated to use more recent methods, such as moving sendRequest -> sendMessage, or chrome.tabs.getSelected -> chrome.tabs.query.
    In all, it appears to work, however.

12
Feb

Transitioning from a DIV to Table-based layout. On purpose.

I’ve been working on a site that uses a App-model style layout;

3 columns, like an Email client.

Screen

It works using Divs laid out with CSS, and generally works really well.

It even loads well on older version of Netscape, properly degrading to run, even if it’s not as pretty.
NS4

The more I look at it, though, the more I wonder if there is more I could do.
If dropping support for ancient browsers is really necessary at all.

Given that it’s a simple 3 column site, my initial thought was to experiment with frames. While these work, dataURI+frames aren’t as well supported as I’d like, and the experience of frames on lynx is not ideal.

What about Tables?

Tables have gotten a bad rap in recent years.
It is generally recommended that we avoid tables, and use divs for layout. But why?

There are few traditional arguments that are given against tables:

  • HTML should be semantic
  • It’s often argued that HTML should be semantic where possible, and to split the presentation (CSS) from the layout (HTML). In practice, however, this often doesn’t work out. To make various tricks work right, developers are often creating a wide variety of presentational divs anyway, for wrappers and other non-semantic content.

    <div class="region region-content">
    <div id="block-system-main" class="block block-system">
    <div class="content">
    <!– Actual page content here –>
    </div>
    </div>
    </div>

    You could certainly argue that we’re moving in a more semantic direction, and HTML5 certainly moves in that direction, but we’re still a long way away from there.
    Combine that with other work arounds that are being done to support non-performant browsers

    [if IE 6]>

    and it’s hard to justify that a simple table is much worse.

  • Screen Readers can’t handle Tables
  • Modern screen readers are actually fairly decently at handling tables. They’ve improved in the last 10 years since we started using CSS.

    Additionally, you can mark a table as presentational, so that it can be interpreted as such using role=”presentation”.

    JAWS, WindowEyes, and NVDA will all do the right thing.

  • CSS lets you just change the one CSS file, rather than every page
  • If I were manually editing 100s of files, this might be a valid concern – But like many modern developers, I’m writing web applications designed to run with a templating system – Python, Ruby, even PHP have libraries which make this trivial. I can change my structure in a single location, and have it automatically apply on every pageload.

  • Table Layouts are only used by people who can’t do divs properly
  • I can sympathize with this view- There’s a lot of people who get stuck in CSS land, and resort to a “Screw it, I’ll use a table” attitude, so I can see where it comes from. But Google and Twitter are using tables as part of their layouts, and I’m sure they have someone there who knows what they’re doing ;)
    In my case, I have the layout working properly in CSS, but I’m deliberately redoing it, spending extra time, to gain the broad-compatibility that a table-based layout brings.
    Some things are easier, some things are harder, but it’s a valid choice in designing a site.

But CSS is really useful!

It sure is!
And I’m in favor of using CSS, all over the place!
Gradients are better than images, rotation is amazing, and changing elements through properties is useful.
CSS has it’s warts (Templating would be nice..), but it’s a great way to explain how elements should look. And divs are often useful for layout, too.
But non-tabular use of a tables is probably OK, as part of a well-balanced breakfast.

29
Jan

Using Mechanical Turk to build a library of Grocery Cards

Envelopes

Earlier this year, I wanted to add the ability to generate and display barcodes as part the Savingstar iPhone app, similar to Loyalty card wallets.
We’re already storing loyalty card numbers of over a million users in order to process their coupons – It seemed like regenerating these into scannable barcodes should be simple enough.
For stores with a scanner that can read a phone (an increasing number), this is a lot more convenient.

Looking over the various card ranges, they fell into a few formats- EAN-13, UPC, etc.

Luckily, there is a library which can easily write these, and easy to use python bindings in order to use them.

Barcode

After a few trials however, I ran into a small issue-

The numbers in the barcode don’t always match up with the printed card number!

Often there is a prefix/postfix on the number, in addition to the checkdigits for UPCs.
After thinking about it for a while, I realized I could reverse engineer the formats, but I’d need cards. Lots of cards.

I turned to Mechanical Turk.

Getting Cards

I posted several jobs, each time asking people to mail me loyalty cards.
Because I needed to match the cards to a something digital to approve/deny them on Mechanical Turk, generated hundreds of random strings, and assigned one to each user.
I then asked each user to then write their particular random string on the envelope; When I received the envelope, I could match it up and give them credit for the job.

And we got cards! Hundreds of beautiful cards, dozens of cards in each of the ranges I needed.
People from around the US and Canada send cards from every major chain in the US.

The biggest downsize to this method is that there is a built in lag.. So even after I had enough cards for each certain chains, and pulled the listing, I still received loads of cards that had already been mailed ;)

In App

12
Dec

Mount KVM images on Linux

Just a quick little script I use once in a while, which I need to mount my KVM images, to access the files manually.

The way it works is to calculate where the data starts, then simply pass that as a paramter to the mount command.

simple, but very useful! ;)


#!/bin/bash
if [ $# -lt 2 ]
        then
        echo "Usage $0 image mountpoint"
        exit
fi

IMAGEFILE=$1
MOUNTPOINT=$2

SECTORSIZE=$(parted -s $IMAGEFILE unit s print | grep "Sector size" | awk -F": " {'print $2'} | awk -F"B/" {'print $1'})
OFFSETBLOCKS=$(parted -s $IMAGEFILE unit s print | grep "^ 3" | awk {'print $2'} | awk -Fs {'print $1'})
OFFSETSECTORS=$(echo "$SECTORSIZE * $OFFSETBLOCKS"  | bc)
mount -o loop,offset=$OFFSETSECTORS $IMAGEFILE $MOUNTPOINT

10
Dec

How to write a simple ajax game loop in Javascript

A friend of mine from the Java world has been trying to get into JS lately.

He understood the basics of how JS works – Functions look (on the outside) superficially similar to Java, it has standard loops/variables, etc.
Canvas functions are very straight forward- The part that confused him was “How do I create a game loop”, and “How do I update the page based on JSON responses from my server?”

Luckily, both of those are REALLY easy in Javascript!

I’m certainly not a Javascript expert, but I put together a simple demonstration, with an over-the-top number of comments.
GameShot

We’ll start with our very simple HTML page.
Anyone who’s done even “Hello World” should understand most of what we’re doing here.

We’re loading our JS, then three divs – Header, Game, and Footer.

<html>
<head>
    <title>This is an example</title>

    <!-- 
         Include are our JS scripts. The "defer" line just tells it to run this after the page loads.
         It's not actually needed, but it makes the initial load faster 
         We're loading in jQuery via a CDN, but copying it locally would work just as well.
         This is just one less file to deal with.
     -->

    <script defer src="http://code.jquery.com/jquery.js"></script>
    <script defer src="exampleCode.js"></script>
</head>

    <!-- 
        In the body (below), define the game however you want it to look.
        I'm using 3 divs, just for the sake of showing we're only showing/changing the game div.
        There's nothing special about this setup.
     -->

<body>
    <div id="Intro">
        This is the beginning of the game.
    </div>
    <div id="Game">
        The GAME GOES HERE.
    </div>
    <div id="Footer">
        This is the bottom.
    </div>
</body>

Now this is calling our JS, exampleCode.js –
That’s also very simple. We’re just going to do two “game loops” to update the page.
One of which updates the color, and the other uses an AJAX request


// This function does the logic of updating our game div.
// Obviously, in a real game, this would be more interesting.
function ChangeColor()
{ 

  // in jQuery, the $ character gives you the root jQuery object.
  // You can use the $(FOO) notation gives you a reference to any element
  // Any element you want. It's just like a CSS selector.
  // So-
  // $('#foo') gives you a HTML element with id="foo"
  // $('.foo') gives you every element with the class="foo" (loop through them)

  // The next line creates an array of random colors.
  bgcolorlist= Array("#DFDFFF", "#FFFFBF", "#80FF80", "#EAEAFF", "#C9FFA8", "#F7F7F7", "#FFFFFF", "#DDDD00");

  // Now, Randomly choose one, by picking a random number (between 1 and arraysize), and then get that array element.
  randombgcolor = bgcolorlist[Math.floor(Math.random()*bgcolorlist.length)];

  // Now, in jQuery, you can set a CSS property, by using the .css function.
  // I'm using it to set the background for the HTML element #Game.

  $("#Game").css('background',randombgcolor);

}

// Let's try a slightly more interesting function. We'll add an external call.
function UpdateTime()
{ 

  // Now, let's do something Ajaxy-
  // There is a URL endpoint for Tavern that gives us the server status, including the time.
  // We can use that to display the current time on our page.

  // The $.ajax() function doesn't return the JSON.. It RUNS the success value as a function.
  // That function will save the variable for us.

  // Normally, we could use $.get(), or $.getJSON(), but these just call $.ajax() under the hood.
  // By calling $.ajax() directly, we can set more options.

  // set the result variable ahead of time, so it's scope isn't only inside the function.
  var result;


  // make the AJAX call
  $.ajax({
    // async defaults to True- This means that the result will wait for a return before running anything else.
    async: false,
    // Setting the dataType to JSON is a convenience. It automatically parses it into a JSON object for us.
    dataType:"json",
    // This is the URL we're pulling from. I set this up to be accessible from any domain.
    // Normally, you can only make requests to the SAME DOMAIN as the HTML runs from.
    // But you can bypass this with a header (Access-Control-Allow-Origin), which I've set for you.
    url: "http://gettavern.com:8090/status",

    // This is just our super-simple function that gets called on success.
    // ALL it's doing is setting the result back to that variable we declared before.

    // Normally, in JS, you'd just do whatever it is you actually needed with your drawing/etc, right in the inline function..
    // Or have it call a longer function, that's defined normally (like the one we're in!)
    // But in this case, I wanted to a) illustrate Inline functions.
    //                           and b) make it short ;)
    success: function(data) {
        result = data;
    }
  });




  // Now, make that into something printable. Notice how we pull values out of the JS call as if it were a dictionary/hashmap?
  timeStampString = "The current timestamp is " + result['timestamp'] + ".";

  // Now, let's DO something with this data-
  // create a new javascript Date object based on the timestamp
  // multiplied by 1000 so that the argument is in milliseconds, not seconds
  var date = new Date(result['timestamp']*1000);
  // hours part from the timestamp
  var hours = date.getHours();
  // minutes part from the timestamp
  var minutes = date.getMinutes();
  // seconds part from the timestamp
  var seconds = date.getSeconds();

  // will display time in 10:30:23 format
  var formattedTime = "
This is equal to " + hours + ":" + minutes + ":" + seconds; // This redefines the HTML inside the game object to whatever we pass it. In this case, it's the time. // Get the Game's DOM object // This gets the instance of the Game, just like we were in the first function. game = $("#Game"); // Replace the Game's HTML. // Just like used the background function in the first example (above), here we're overwriting the HTML that's in the Game object. // Everytime this runs, everthing that is in the game div is erased, and replaced with whatever string we pass. // In this case, we're passing in our two time-based strings we just created. game.html(timeStampString + formattedTime); } // Run the code above, every 500ms self.setInterval("ChangeColor()",500); self.setInterval("UpdateTime()",1000);

You can see this in action at jsfiddle.

It’s all very very straightforward code, but it occured to me that it might be useful to others, so I’ve archived it here for anyone who finds it ;)

5
Dec

iStat Menus demotes user’s licenses with sneaky upgrade

For the last few years, I’ve been using a cute little tool called iStat menus to graph my CPU/Memory in my address bar.
It doesn’t do that much, but it works, it stays out of my way, and the UI is better than the nearest free alternative.

Menu2

I don’t think about the tool very much – Once in a while I’ll glance up when the system is running slow, or use the clock function to see the time in multiple time-zones.

Every so often, there’s a new version available, and the software prompts me to upgrade –

Upg1 2

Upg2 3
I generally go ahead and agree to the change, and go about my business.

Although my 1999 self would cry to hear me say it, I don’t always read through the changelogs-

We get them everywhere now – On my Android Phone (Yep, go ahead, update), iPhone (Sure. Update), OSX (OK, add the new release), etc.

In my professional life managing servers, I review the changes, weigh the pros and cons, and decide if I want to upgrade – But for a simple Menu app, that seems overkill.

This week, iStat released another upgrade, and once more I upgraded without really paying much attention.
I probably should have, but realistically, I don’t think most users will.
Upg3

As you can see in the screenshot however, this upgrade to iStat Pro 4 is a paid upgrade.
Accepting this upgrade downgraded my paid software to 14-day trial status.

Trial

I’m not the only one who felt that wasn’t cool.

iStat is offering upgrade pricing ($9), and the app isn’t that expensive in and of itself, but the entire process felt very sketchy.[1]

Users are used to accepting in-app upgrades, and switching one out for a paid-upgrade feels very disingenuous, regardless of their justifications.
Bjango (the creators) could have handled things differently,by setting the “Remind me Later” Dialog option to be the blue/rightmost option, or even installing as a different name in the Applications folder, so you could still easily fire up v3.

Admittedly, It’s a minor issue, $9 isn’t much, and I don’t begrudge an indie shop trying to raise money for a new version and stand in business, but the sense of fairplay has been violated- It’s one more reason for me to try to avoid buying non App Store applications in the future.

For anyone else who runs into this, You can uninstall iStat 4 using the menu item-
Uninstall
And then reinstall Version 3.


[1]For what it’s worth, I did end up buying the upgrade. The UI is a bit nicer.
This isn’t about the $9. It’s about the process.

28
Nov

Fun with Framesets in 2012

One of the things I’ve noticed in web development is a trend toward avoiding older technologies.

There’s often a good reason for this – Newer replacements allow more flexibility, work more reliably, and are better supported.

But that doesn’t mean we should always abandon old friends – In this case, I mean the humble HTML frame.



Although deprecated in HTML5, there’s still fun to be had.

But aren’t frames evil?

One of the biggest criticisms of frames is that they aren’t reliably accessed – Their very nature allow search-engines (or users) to link to one specific frame, rather than to the frameset container you’re wrapping them with.


It turns out, we have a weapon against this, which has been around since 1998- The DataURI.

Rather than linking to a specific file, using the datauri, we can embed our frame elements directly INSIDE the original page.
They can be generated at the same time, with the rest of your code, and stored directly.
This eliminates the idea of a separate page that might ever accidentally get linked to.

<frameset cols="30%,*">
        <frame src="data:text/html;base64,CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPHRpdGxlPkZyYW1lIE1lbnU8L3RpdGxlPgogICAgICAgIDxiYXNlIHRhcmdldD0iY29udGVudCI+CiAgICA8L2hlYWQ+CiAgICA8Ym9keSBiZ2NvbG9yPSIjMDA5OUREIj4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3LnlhaG9vLmNvbSIgY2xhc3M9Im1lbnUiPllhaG9vLmNvbTwvYT48YnIgLz4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3Lmdvb2dsZS5jb20iIGNsYXNzPSJtZW51Ij5Hb29nbGUuY29tPC9hPjxiciAvPgogICAgICAgIDxociAvPgogICAgICAgIDxhIGhyZWY9Imh0dHA6Ly93d3cuZWJheS5jb20iIGNsYXNzPSJtZW51Ij5FYmF5LmNvbTwvYT48YnIgLz4KICAgICAgICA8YSBocmVmPSJodHRwOi8vd3d3LmZhbmRhbmdvLmNvbSIgY2xhc3M9Im1lbnUiPkZhbmRhbmdvLmNvbTwvYT48YnIgLz4KICAgIDwvYm9keT4KPC9odG1sPgo=">
        <frame src="data:text/html;base64,CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPHRpdGxlPkZyYW1lIENvbnRlbnQ8L3RpdGxlPgogICAgPC9oZWFkPgogICAgPGJvZHkgYmdjb2xvcj0iI0RERERERCI+CiAgICAgICAgPHA+SGVyZSdzIHdoYXQgaXQgd291bGQgbG9vayBsaWtlQSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5BIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLjwvcD4KICAgICAgICA8cD5IZXJlJ3Mgd2hhdCBpdCB3b3VsZCBsb29rIGxpa2VBIGdvb2QgcnVsZSBvZiB0aHVtYiBpcyB0byBjYWxsIHRoZSBwYWdlIHdoaWNoIGNvbnRhaW5zIHRoaXMgZnJhbWUgaW5mb3JtYXRpb24gImluZGV4Lmh0bWwiIGJlY2F1c2UgdGhhdCBpcyB0eXBpY2FsbHkgYSBzaXRlJ3MgbWFpbiBwYWdlLgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuPC9wPgogICAgICAgIDxwPkhlcmUncyB3aGF0IGl0IHdvdWxkIGxvb2sgbGlrZUEgZ29vZCBydWxlIG9mIHRodW1iIGlzIHRvIGNhbGwgdGhlIHBhZ2Ugd2hpY2ggY29udGFpbnMgdGhpcyBmcmFtZSBpbmZvcm1hdGlvbiAiaW5kZXguaHRtbCIgYmVjYXVzZSB0aGF0IGlzIHR5cGljYWxseSBhIHNpdGUncyBtYWluIHBhZ2UuCiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICAgICAgPHA+QSBnb29kIHJ1bGUgb2YgdGh1bWIgaXMgdG8gY2FsbCB0aGUgcGFnZSB3aGljaCBjb250YWlucyB0aGlzIGZyYW1lIGluZm9ybWF0aW9uICJpbmRleC5odG1sIiBiZWNhdXNlIHRoYXQgaXMgdHlwaWNhbGx5IGEgc2l0ZSdzIG1haW4gcGFnZS48L3A+CiAgICA8L2JvZHk+CjwvaHRtbD4KCg==">
    </frameset>

See this in action

If your browser supports the “data:text/html;charset=UTF-8,” type (webkit seems to like this better than others) you can make this even easier, and embed your normal HTML, straight into the original page.

This will work somewhat similarly to a resizable-div, but using 1998-level technology.


<frameset cols="30%,*">
        <frame src="data:text/html;charset=UTF-8,<html>
    <head>
        <title>Frame Content</title>
    </head>
    <body bgcolor='#DDDDDD'>
        <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>Here's what it would look likeA good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
        <p>A good rule of thumb is to call the page which contains this frame information "index.html" because that is typically a site's main page.</p>
    </body>
</html>">
        <frame src="data:text/html;charset=UTF-8,
<html>
    <head>
        <title>Frame Menu</title>
        <base target='content'>
    </head>
    <body bgcolor='#0099DD'>
        <a href='http://www.yahoo.com' class='menu'>Yahoo.com</a><br />
        <a href='http://www.google.com' class='menu'>Google.com</a><br />
        <hr />
        <a href='http://www.ebay.com' class='menu'>Ebay.com</a><br />
        <a href='http://www.fandango.com' class='menu'>Fandango.com</a><br />
    </body>
</html>">
    </frameset>

See this in action

Why would I use this?

Honestly, I don’t think that anyone should ;)
But it does have certain advantages over “Splitter” style javascript –

  • It’ll work without JS enabled
  • It’s compatible with old browsers
  • The browser does the work of resizing, rather than requiring it to happen in JS



In all, it’s a fun hack, and I figured it might appeal to other people who haven’t forgotten about some of the older layers still with us in our browsers.

Of course, while it might work in Netscape 3.0, like anything fun, it doesn’t work in older versions of IE.

Follow

Get every new post delivered to your Inbox.