2011-07-25

Fuck You, OS X!

The joys of a commercial operating system! I was happy to play Diablo 2 on my shiny new MacBook Air. No, not one of the cool new-new MacBook Airs, the slightly older MacBook air that still has discreet graphics. Anyway, I decided to finally bite the bullet and pay the $5 for the latest Xcode. I was happy to discover that it's now free. Then I noticed that it was free for Lion, only. So I paid the $30 and upgraded. Then I discovered that I couldn't play Diablo 2 anymore. Then I discovered that I probably could have gotten the upgrade to Lion for free, through Apple, because I bought my system so close to the release date of Lion.

Well, bitterness aside, I decided to grab wine for OS X and use that to launch a windows install of Diablo 2. Then once that was working, I decided to package it up in a .app file so that I could at least pretend it was a native app. After that, I discovered there's something called wineskin that will let you package stuff up into .app bundles. Eh, whatever, I already made the damn script, put the right files in the right places, and through some guesswork figured out how to get it to all play nicely. Then I tweaked my script so that it would search for any old wine installation within the bundle, in case I upgrade the version of wine it's using. Then I tweaked the script a bit further to have it configure the 800x600 desktop so that the game would stop fucking up my graphics and displaying incorrectly in the process. It was weird, the game would launch, it looked great, but the window was getting pushed off the screen, ever so slightly. The emulated desktop fixed that.

Since I had it configure wine, I decided to remove the stupid gecko prompt for HTML support in windows applications. Maybe battle.net makes use of the HTML display, I don't know, the game certainly didn't need it for multi-player.

Anyway, here's the bizarre stuff that I learned in the process of creating the .app. I knew that .apps were folders, but I didn't know what the structure needed to look like, so I took a peek at Torchlight. Nice and simple. I mimicked the directory structure so that I had the following:
Diablo2.app/Contents/MacOS
Diablo2.app/Contents/MacOS/Info.plist
Diablo2.app/Contents/MacOS/diablo2.icns (stole from somewhere)
Diablo2.app/Contents/MacOS/diablo2.sh (script I wrote)
Diablo2.app/Contents/Resources
Diablo2.app/Contents/Resources/diablo2.icns ->
../MacOS/diablo2.icns
Diablo2.app/Contents/PkgInfo
Diablo2.app/Contents/Info.plist -> ./MacOS/Info.plist

When it was all said and done, I had the following additional items:
Diablo2.app/Contents/MacOS/1.3.24     (wine distro)
Diablo2.app/Contents/MacOS/Diablo II (from windows)
Diablo2.app/Contents/MacOS/prefix (generated)

[Edit: Here's where I got the icon set: http://www.iconeasy.com/icon/diablo-ii-icon/ No idea where they got it from, so I don't know that this is really proper attribution.]

So, here's the cool part. Parameter Expansion. The diablo2.sh I created was modeled after the Steam.app steam.sh script. And when I say modeled after, what I really mean is that I read the first executable line of the script and said, "What the hell is this?"

Here's the line in question:
STEAMROOT=$(cd "${0%/*}" && echo $PWD)

I gathered that PWD was feeding the PWD to the variable STEAMROOT, so I figured the ${0%/*} must be doing something crazy to $0. So the first thing I did was start issuing echo commands and trying things with different variables. Now, I could see that the ${0%/*} was taking whatever the value of the variable was and chopping off the trailing file name but I didn't understand what sorcery was allowing this. Turns out bash has some built-in awesomeness that I just didn't bother to notice in the 10 years or so that I've been using it. I'm so so sorry for ignoring you, bash! If you look at the man page for bash, it will explain (among other things) ${VAR%pattern}, ${VAR#pattern}, and ${VAR/pattern/substitute/}. They work much like you can imagine. The ${VAR%pattern) removes the pattern, starting its search from the right side of the value and searching towards the left, going for the least-greedy approach. The ${VAR#pattern} similarly removes the pattern, but starting from the left. The ${VAR/pattern/substitute} is something that I've needed ever since I started using Linux back in high school. It's like a sed expression. It replaces the specified pattern with the specified substition. It's all so bloody awesome.

Up until now, I had been doing this to get the directory part of a file name:
DIR="`echo $FULLPATH| rev | cut -f1- -d/ | rev`"

I just needed to do this:
DIR="${FULLPATH%/*}"

I cannot count the number of times that I've shelled out to imagemagick to process a directory and have done something foolish like this:
for f in *.jpg ; do NEW_NAME="`echo $f | rev | cut -f2- -d'/' | rev`.png" convert "$f" "$NEW_NAME" ; done

And I could have just done this:
for f in *.jpg ; do convert "${f}" "${f/\.jpg/.png}; done

So, getting back to the Diablo stuff. I don't know why the steam.sh does the cd and pwd when it could just evaluate the variable, but whatever; maybe they use a side-effect later. Here's my diablo2.sh file:


On to the other package details...

Info.plist, ties everything in the package together:

PkgInfo only contains this line:
APPL????

Here's a crazy script I threw together, simply invoke it and it will base64 decode the last line of it's content, reconstitute a tar.bz2 file, md5 it for integrity, and if all went well, it will extract a copy of my Diablo2.app folder, minus the wine and minus my Diablo 2 install folder. Just get any one of the pre-built wine for OS X from here: http://wine.playonlinux.com/darwin-i386/ and extract it into the MacOS folder of the Diablo2.app. Then, copy in your Diablo II from a windows install, and the script will locate all the pieces and launch. Be prepared for sounds, because the script speaks its status messages.

2011-07-03

OS X Right Click Menu Additions

I'm sure this is documented somewhere, but I didn't find it while looking. If you use the Automator to create a service that receives files or folders in any application, it will get added to the right click menu. Now I can right click documents and choose to edit them with Vim.

I noticed this while trying to add a hotkey to the service. The hotkey didn't work, but then I noticed my edit option in the menu, so that's almost as good.


So here's how I created a right-click option for Edit with Vim under Snow Leopard:

  • Launch Automator

  • Choose The Service Template

  • Change "Service receives selected" pulldown option from "text" to "files or folders."

  • Drag "Run Shell Script" from the library into the current workflow

  • Change the "Pass input" pulldown from "to stdin" to "as arguments"

  • Delete the text from the edit box and enter the following:


  • Save it as something concise, but descriptive, like: Edit with single Vim



This time I embedded everything for the command in the shell script of the service because I'll only ever need to call this from the gui.


Edit: Update: OS X Lion treats Applications like services. If you create an Application with the Automator, it will likewise show up in your services menu.

2011-07-02

OS X DMG Creation from a Folder

Edit: As of OS X Lion (10.7) Applications show up in your services menu, so you get a right click menu for free.


The hdiutil command is awesome. Amongst its many features is the ability to create a DMG file from a specified folder. For some reason, there's no right click option for creating DMG files from folders. Strangely enough, there is a right click compress option that will create a zip, but that's really not sufficient. If you create a zip, you can't access individual files from it like you can a compressed DMG.

Edit: For anyone who just wants the command and not fuss with the scripts, here's how you might create a zip compressed dmg file from a folder called Movies:
hdiutil create -srcfolder Movies -volname Movies -fs HFS+ \
-fsargs -c c=64,a=16,e=16 -format UDBZ ./Movies.dmg

Once again, I've used Automator to invoke a bash script. Now I can drop folders on my app and generate a compressed, read-only DMG file. I don't have lots of free space, so I use this for applications I've downloaded that come as a tar.gz/tar.bz/zip/whatever.

I'd like to point out that while it is possible to embed all of the bash script content into the Automator "Run Shell Script" feature, but then I wouldn't be able to easily invoke it from the command line and I use the command line alot.

Here's the content of my Automator "Run Shell Script" box (CreateDMGFromFolder.app):


Here's the content of my shell script (CreateDMGFromFolder):


Edit: I've updated the script in light of reading the bash man page. Parameter Expansion is bad-ass.

Here's the new shell script to replace the aforementioned (CreateDMGFromFolder):

2011-07-01

OS X Open With Vim

Edit: After my second OS X post, I went back and created a service to perform Edit with Vim. I actually forgot about the .app that I made with automator. Then I upgraded to Lion and was all sorts of confused when two entries for Edit with Vim popped up in my context menu. Wouldn't you know it? Lion puts all automator Services and Applications into the right click context menu. So I've actually deleted this script and am using the one mentioned here.

OS X comes with vim preinstalled. Nice. It's not that retarded version Ubuntu comes with by default, either. It's the full vim. So then, why the hell can't I right click something, choose open with, and then choose vim? Why can't I pass arguments to the terminal to tell it to run arbitrary commands (such as vim) from the open with dialog?

It appears that OS X only lets you pick Apps as the target application for performing an "Open with". So, to get what I want, I have to use Automator in conjunction with a bash shell script in order to run osascript in order to pop up a new terminal with vim in it and whatever files are selected (or dropped on the app).

Here's what I have in my bash script, aptly named TellTerminal:


This script sends whatever it's arguments are to the Terminal.app and tells it to run them. Then it tells the Terminal.app to activate itself so that it jumps to the foreground. Without this, the terminal will still open, but there's a chance that it will be sitting in the background somewhere. A really, really good chance.

To invoke this script, I had to create an Automator Application (not a workspace, only the .app can be invoked as an application) and add an action 'Run shell script'. This is the shell script that it runs:



This script (which only exists in the Automator app that I've named EditWithVim.app,) loops over all the arguments that were passed to it in order to invoke vim and pass it the quoted file names. This is to avoid having a space within a file name break my script. Also, this script looks for a BashScripts directory in my user's Library directory within the user's home.

Now I can set things to be opened with vim. It's extremely convoluted, but it does seem to be working.