diff --git a/CursesUIMInstance.hs b/CursesUIMInstance.hs
--- a/CursesUIMInstance.hs
+++ b/CursesUIMInstance.hs
@@ -14,6 +14,7 @@
 import qualified UI.HSCurses.Curses as Curses
 import qualified UI.HSCurses.CursesHelper as CursesH
 import Control.Concurrent.STM
+import Control.Concurrent
 import Control.Applicative
 import Data.Char (ord)
 import Data.Monoid
@@ -366,7 +367,12 @@
 
     toggleColourMode = modify $ \s -> s {monochrome = not $ monochrome s}
 
-    getDrawImpatience = return $ \_ -> return ()
+    impatience ticks = do
+	when (ticks>20) $ say "Waiting for server (^C to abort)..."
+	unblock <- unblockInput
+	liftIO $ forkIO $ threadDelay 50000 >> unblock
+	cmds <- getInput IMImpatience
+	return $ CmdQuit `elem` cmds
 
     getInput mode = do
 	let userResizeCode = 1337  -- XXX: chosen not to conflict with HSCurses codes
diff --git a/InputMode.hs b/InputMode.hs
--- a/InputMode.hs
+++ b/InputMode.hs
@@ -10,5 +10,5 @@
 
 module InputMode where
 
-data InputMode = IMEdit | IMPlay | IMReplay | IMMeta | IMTextInput
+data InputMode = IMEdit | IMPlay | IMReplay | IMMeta | IMTextInput | IMImpatience
     deriving (Eq, Ord, Show, Read)
diff --git a/Interact.hs b/Interact.hs
--- a/Interact.hs
+++ b/Interact.hs
@@ -511,9 +511,10 @@
 		Just $ WrenchPush dir
 	    | otherwise = Just $ HookPush dir
 	torque whs dir
-	    | whs == WHSHook || (whs == WHSSelected && not wsel)=
+	    {- | whs == WHSHook || (whs == WHSSelected && not wsel)=
 		Just $ HookTorque dir
-	    | otherwise = Nothing
+	    | otherwise = Nothing -}
+	    = Just $ HookTorque dir
 	(wsel', pm) =
 	    case cmd of
 		CmdTile (WrenchTile _) -> (True, Nothing)
diff --git a/InteractUtil.hs b/InteractUtil.hs
--- a/InteractUtil.hs
+++ b/InteractUtil.hs
@@ -173,7 +173,7 @@
 		Nothing -> waitConfirm
 	ansOfCmd (CmdInputChar 'y') = Just True
 	ansOfCmd (CmdInputChar 'Y') = Just True
-	ansOfCmd CmdRedraw = Nothing
+	ansOfCmd CmdRedraw = Just False
 	ansOfCmd CmdRefresh = Nothing
 	ansOfCmd CmdUnselect = Nothing
 	ansOfCmd _ = Just False
@@ -229,7 +229,6 @@
 		    Right $ (name++[':',lockIndexChar idx], Nothing)
 		applyCmd x (CmdInputCodename name) =
 		    Right $ (name, Nothing)
-		applyCmd x CmdRedraw = Right x
 		applyCmd x CmdRefresh = Right x
 		applyCmd x CmdUnselect = Right x
 		applyCmd _ _ = Left False
diff --git a/KeyBindings.hs b/KeyBindings.hs
--- a/KeyBindings.hs
+++ b/KeyBindings.hs
@@ -186,11 +186,16 @@
     , ('<', CmdPrevPage)
     ] ++ miscGlobal
 
+impatienceBindings = lowerToo $
+    [ ('Q', CmdQuit)
+    , (ctrl 'C', CmdQuit) ]
+
 bindings :: InputMode -> KeyBindings
 bindings IMEdit = editBindings
 bindings IMPlay = playBindings
 bindings IMMeta = metaBindings
 bindings IMReplay = replayBindings
+bindings IMImpatience = impatienceBindings
 bindings _ = []
 
 findBindings :: KeyBindings -> Command -> [Char]
diff --git a/MainBoth.hs b/MainBoth.hs
--- a/MainBoth.hs
+++ b/MainBoth.hs
@@ -8,6 +8,10 @@
 -- You should have received a copy of the GNU General Public License
 -- along with this program.  If not, see http://www.gnu.org/licenses/.
 
+{-# OPTIONS_GHC -cpp #-}
+#ifdef APPLE
+{-# LANGUAGE ForeignFunctionInterface #-}
+#endif
 module Main where
 
 import Init
@@ -16,6 +20,13 @@
 import qualified CursesUI (UIM)
 import CursesUIMInstance ()
 import MainState
+
+#ifdef APPLE
+foreign export ccall hs_MAIN :: IO ()
+
+hs_MAIN :: IO ()
+hs_MAIN = main
+#endif
 
 main = main'
     (Just (doUI::SDLUI.UIM MainState -> IO (Maybe MainState)))
diff --git a/MainSDL.hs b/MainSDL.hs
--- a/MainSDL.hs
+++ b/MainSDL.hs
@@ -8,12 +8,23 @@
 -- You should have received a copy of the GNU General Public License
 -- along with this program.  If not, see http://www.gnu.org/licenses/.
 
+{-# OPTIONS_GHC -cpp #-}
+#ifdef APPLE
+{-# LANGUAGE ForeignFunctionInterface #-}
+#endif
 module Main where
 
 import Init
 import qualified SDLUI
 import SDLUIMInstance ()
 import MainState
+
+#ifdef APPLE
+foreign export ccall hs_MAIN :: IO ()
+
+hs_MAIN :: IO ()
+hs_MAIN = main
+#endif
 
 main = main'
     (Just (doUI::SDLUI.UIM MainState -> IO (Maybe MainState)))
diff --git a/MainState.hs b/MainState.hs
--- a/MainState.hs
+++ b/MainState.hs
@@ -62,7 +62,7 @@
     unblockInput :: m (IO ())
     setUIBinding :: InputMode -> Command -> Char -> m ()
     getUIBinding :: InputMode -> Command -> m String
-    getDrawImpatience :: m ( Int -> IO () )
+    impatience :: Int -> m Bool
     toggleColourMode :: m ()
     warpPointer :: HexPos -> m ()
     getUIMousePos :: m (Maybe HexPos)
@@ -296,7 +296,7 @@
     modify $ \ms -> ms { listOffset = 0 }
     mourNameSelected >>? getRandomNames
     lift $ modify $ \ms -> ms {retiredLocks = Nothing}
-    lift.lift $ drawMessage ""
+    --lift.lift $ drawMessage ""
     where
 	getRandomNames = do
 	    rnamestvar <- gets randomCodenames
@@ -337,7 +337,8 @@
     auth <- gets curAuth
     cOnly <- gets cacheOnly
     if cOnly then return $ ServerError "Can't contact server in cache-only mode"
-	else lift $ withImpatience $ liftIO $ makeRequest saddr $ ClientRequest protocolVersion auth act
+	else (fromMaybe (ServerError "Request aborted") <$>) $
+	    lift $ withImpatience $ makeRequest saddr $ ClientRequest protocolVersion auth act
 
 curServerActionAsyncThenInvalidate :: UIMonad uiM => Protocol.Action -> Maybe Codenames -> MainStateT uiM ()
 curServerActionAsyncThenInvalidate act names = do
@@ -384,30 +385,32 @@
 getFreshRecBlocking rec = do
     tvar <- getRecordCachedFromCur False rec
     cOnly <- gets cacheOnly
-    fetched <- lift $ withImpatience $ liftIO $ atomically $ do
+    mfetched <- lift $ withImpatience $ atomically $ do
 	fetched@(FetchedRecord fresh _ _) <- readTVar tvar
 	check $ fresh || cOnly
 	return fetched
-    case fetchError fetched of
-	Nothing -> return $ fetchedRC fetched
-	Just err -> lift (drawError err) >> return Nothing
+    case mfetched of
+	Nothing -> lift (drawError "Request aborted") >> return Nothing
+	Just fetched ->
+	    case fetchError fetched of
+		Nothing -> return $ fetchedRC fetched
+		Just err -> lift (drawError err) >> return Nothing
 
--- |draw indication that we're waiting for the server while we do so
-withImpatience :: UIMonad uiM => uiM a -> uiM a
+-- |indicate waiting for server, and allow cancellation
+withImpatience :: UIMonad uiM => IO a -> uiM (Maybe a)
 withImpatience m = do
-    drawImpatience <- getDrawImpatience
-    finishedTV <- liftIO $ atomically $ newTVar False
+    finishedTV <- liftIO $ atomically $ newTVar Nothing
+    id <- liftIO $ forkIO $ m >>= atomically . writeTVar finishedTV . Just
     let waitImpatiently ticks = do
-	    wakeTV <- atomically $ newTVar False
-	    forkIO $ threadDelay (10^6) >> atomically (writeTVar wakeTV True)
-	    finished <- atomically $ do
-		wake <- readTVar wakeTV
-		finished <- readTVar finishedTV
-		check $ wake || finished
-		return finished
-	    when (not finished) $ drawImpatience (ticks+1) >> waitImpatiently (ticks+1)
-    liftIO $ forkIO $ waitImpatiently 0
-    m <* (liftIO $ atomically $ writeTVar finishedTV True)
+	finished <- liftIO $ atomically $ readTVar finishedTV
+	if isJust finished
+	    then return finished
+	    else do
+		abort <- impatience ticks
+		if abort
+		    then liftIO $ killThread id >> return Nothing
+		    else waitImpatiently $ ticks+1
+    waitImpatiently 0
 
 
 getRelScore :: (UIMonad uiM) => Codename -> MainStateT uiM (Maybe Int)
@@ -451,22 +454,22 @@
 metagameHelpText =
     [ "By ruthlessly guarded secret arrangement, the Council's agents can pick any lock in the city."
     , "The Guild produces the necessary locks - apparently secure, but with fatal hidden flaws."
-    , "The ritual game known as \"Intricacy\" is played to determine the best designs."
-    , "Players attempt to design locks which can be picked only by one who knows the secret,"
-    , "and try to discover the secret flaws in the designs of their colleagues."
+    , "A ritual game is played to determine the best designs."
+    , "To play the game well, you must build locks which can be picked only by one who knows the secret,"
+    , "and you must discover the secret flaws in the locks designed by your colleagues."
     , ""
     , "You may put forward up to three prototype locks. They will guard the secrets you discover."
-    , "If you pick a colleague's lock, the rules require that a note be written describing your solution."
-    , "The composition and deciphering of notes is an art in itself, whose details do not concern us here,"
-    , "but a note proves that the author found a solution, while revealing as little detail as possible."
+    , "If you pick a colleague's lock, the rules require that you have a note written on your solution."
+    , "A note proves that a solution was found, while revealing no more of its details than necessary."
+    , "Composing notes is a tricky and ritual-bound art of its own, performed by independent experts."
     , "To declare your success, you must secure your note behind a lock of your own."
     , "If you are able to unlock a lock, you automatically read all the notes it secures."
-    , "If you read three notes on a lock, you will piece together the clues and work out how to solve it."
+    , "If you read three notes on a lock, you will piece together the secrets of unlocking that lock."
     , ""
-    , "Players are judged relative to each of their peers. There are no absolute rankings."
-    , "You win a point of esteem against another player for one of their locks if either:"
-    , "you have declared a note on the lock and the lock's owner has not read that note,"
-    , "or if you have read three notes on the lock."
+    , "The game judges players relative to each of their peers. There are no absolute rankings."
+    , "You win a point of esteem against another player for each of their locks for which either:"
+    , "you have declared a note on the lock which the lock's owner has not read,"
+    , "or you have read three notes on the lock."
     , "Relative esteem ranges from +3 (best) to -3 (worst), and is calculated as"
     , "the number of their locks which win you a point minus the number of your locks which win them one."
     , "If the secrets to one of your locks become widely disseminated, you may wish to replace it."
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,11 @@
 This is an abbreviated summary; see the git log for gory details.
 
+0.5.7:
+    Allow cancellation of server requests if the server doesn't respond.
+    Minor UI tweaks.
+0.5.6:
+    Support OS X (thanks Kevin Eaves).
+    Rework setting dimensions.
 0.5.5:
     Save solutions-in-progress of locks and tutorial.
     Indicate when there's a pending request.
diff --git a/SDLUI.hs b/SDLUI.hs
--- a/SDLUI.hs
+++ b/SDLUI.hs
@@ -23,6 +23,7 @@
 import Control.Monad.State
 import Control.Monad.Trans.Maybe
 import Control.Monad.Trans.Reader
+import Control.Arrow
 import Data.Word
 import Data.Array
 import Data.List
@@ -173,7 +174,7 @@
 	    markGroup = ([Button (tl+^hw) CmdMark [("set",hu+^neg hw),("mark",hu+^neg hv)]
 		, Button (tl+^hw+^hv) CmdJumpMark [("jump",hu+^neg hw),("mark",hu+^neg hv)]
 		, Button (tl+^hw+^2*^hv) CmdReset [("jump",hu+^neg hw),("start",hu+^neg hv)]],(0,1))
-	    global = if mode == IMTextInput then [] else
+	    global = if mode `elem` [IMTextInput,IMImpatience] then [] else
 		[ singleButton br CmdQuit 0 [("quit",hu+^neg hw)]
 		, singleButton (tr +^ 3*^hv +^ 3*^hu) CmdHelp 3 [("help",hu+^neg hw)] ]
 	    whsBGs :: Maybe WrHoSel -> InputMode -> [ ButtonGroup ]
@@ -291,33 +292,33 @@
 helpOfSelectable (SelLockUnset False _) = Just
     "An empty lock slot."
 helpOfSelectable (SelUndeclared _) = Just
-    "Declare yourself privy to a lock's secrets by securing a note on it behind a lock of your own."
+    "Declare yourself able to unlock a lock by securing a note on it behind a lock of your own."
 helpOfSelectable (SelRandom _) = Just
     "Random set of guild members. Colours show relative esteem, bright red (-3) to bright green (+3)."
 helpOfSelectable (SelScoreLock (Just name) Nothing _) = Just $
-    "Your lock, the secrets of which "++name++" is not privy to."
+    "Your lock, which "++name++" can not unlock."
 helpOfSelectable (SelScoreLock (Just name) (Just AccessedPrivyRead) _) = Just $
-    "Your lock, the secrets of which "++name++" has read three notes on: -1 to relative esteem."
+    "Your lock, on which "++name++" has read three notes: -1 to relative esteem."
 helpOfSelectable (SelScoreLock (Just name) (Just (AccessedPrivySolved False)) _) = Just $
     "Your lock, on which "++name++" has declared a note which you have not read: -1 to relative esteem."
 helpOfSelectable (SelScoreLock (Just name) (Just (AccessedPrivySolved True)) _) = Just $
-    "Your lock, on which "++name++" has declared a note which you have read."
+    "Your lock, on which "++name++" has declared a note which you have, however, read."
 helpOfSelectable (SelScoreLock (Just name) (Just AccessedPub) _) = Just $
     "Your lock, the secrets of which have been publically revealed: -1 to relative esteem."
 helpOfSelectable (SelScoreLock (Just name) (Just AccessedEmpty) _) = Just $
-    "Your empty lock slot; "++name++" is privy to the secrets of all your locks: -1 to relative esteem."
+    "Your empty lock slot; "++name++" can unlock all your locks: -1 to relative esteem."
 helpOfSelectable (SelScoreLock Nothing Nothing (ActiveLock name _)) = Just $
-    name++"'s lock, the secrets of which you are not privy to."
+    name++"'s lock, which you can not unlock."
 helpOfSelectable (SelScoreLock Nothing (Just AccessedPrivyRead) (ActiveLock name _)) = Just $
-    name++"'s lock, the secrets of which you have read three notes on: +1 to relative esteem."
+    name++"'s lock, on which you have read three notes: +1 to relative esteem."
 helpOfSelectable (SelScoreLock Nothing (Just (AccessedPrivySolved False)) (ActiveLock name _)) = Just $
     name++"'s lock, on which you have declared a note which "++name++" has not read: +1 to relative esteem."
 helpOfSelectable (SelScoreLock Nothing (Just (AccessedPrivySolved True)) (ActiveLock name _)) = Just $
-    name++"'s lock, on which you have declared a note which "++name++" has read."
+    name++"'s lock, on which you have declared a note which "++name++" has, however, read."
 helpOfSelectable (SelScoreLock Nothing (Just AccessedPub) (ActiveLock name _)) = Just $
     name++"'s lock, the secrets of which have been publically revealed: +1 to relative esteem."
 helpOfSelectable (SelScoreLock Nothing (Just AccessedEmpty) (ActiveLock name _)) = Just $
-    name++"'s empty lock slot; you are privy to the secrets of all "++name++"'s locks: +1 to relative esteem."
+    name++"'s empty lock slot; you can unlock all "++name++"'s locks: +1 to relative esteem."
 helpOfSelectable (SelReadNote note) = Just $
     "You have read "++noteAuthor note++"'s note on this lock."
 helpOfSelectable SelReadNoteSlot = Just $
@@ -329,7 +330,7 @@
 	" has secured their note on this lock behind their lock " ++ [lockIndexChar idx] ++ "."
     Nothing -> noteAuthor note ++ "'s note on this lock is public knowledge."
 helpOfSelectable (SelAccessed name) = Just $
-    name ++ " did not pick this lock, but by reading three notes became privy its secrets."
+    name ++ " did not pick this lock, but learnt how to unlock it by reading three notes on it."
 helpOfSelectable (SelPublicLock) = Just
     "Notes behind retired or public locks are public; locks with three public notes are public."
 helpOfSelectable (SelAccessedInfo meth) = Just $ case meth of
@@ -343,7 +344,7 @@
 helpOfSelectable SelLockPath = Just $
     "Select a lock by its name. The names you give your locks are not revealed to others."
 helpOfSelectable SelPrivyHeader = Just $
-    "Fellow guild members privy to this lock's secrets, hence able to read its secured notes."
+    "Fellow guild members able to unlock this lock, hence able to read its secured notes."
 helpOfSelectable SelNotesHeader = Just $
     "Secured notes. Notes are obfuscated sketches of method, proving success but revealing little."
 helpOfSelectable SelToolWrench = Just $ "The wrench, one of your lockpicking tools. Click and drag to move."
@@ -714,14 +715,29 @@
 	]
     where enumerate = zip [0..]
 
+initMisc :: IO ()
+initMisc = void $ enableUnicode True >> enableKeyRepeat 250 30 >> setCaption "intricacy" "intricacy"
+
 initVideo :: Int -> Int -> UIM ()
 initVideo w h = do
+    liftIO $ (((w,h)==(0,0) &&) . (InitVideo `elem`) <$> wasInit [InitVideo]) >>?
+	-- reset video so that passing (0,0) to setVideoMode sets to
+	-- current screen res rather than current window size
+	(quitSubSystem [InitVideo] >> initSubSystem [InitVideo] >> initMisc)
+
     fs <- fullscreen <$> gets uiOptions
-    liftIO $ setVideoMode w h 0 [if fs then Fullscreen else Resizable]
+    liftIO $ do
+	(w',h') <- if (fs || (w,h)/=(0,0)) then return (w,h) else do
+	    -- use smaller dimensions than the screen's, to work around a bug
+	    -- seen on mac, whereby a resizable window created with
+	    -- (w,h)=(0,0), or even with the (w,h) given by getDimensions
+	    -- after creating such a window, is reported to be larger than it
+	    -- is.
+	    (w',h') <- getDimensions
+	    return $ (4*w'`div`5,4*h'`div`5)
+	setVideoMode w' h' 0 $ if fs then [Fullscreen] else [Resizable]
 
-    -- see what size we actually got:
-    vinfo <- liftIO $ getVideoInfo
-    let [w',h'] = map ($vinfo) [videoInfoWidth,videoInfoHeight]
+    (w',h') <- liftIO getDimensions
 
     modify $ \ds -> ds { scrWidth = w' }
     modify $ \ds -> ds { scrHeight = h' }
@@ -750,6 +766,10 @@
 	putStr text
 	writeFile "error.log" text
 
+    where
+	getDimensions = (videoInfoWidth &&& videoInfoHeight) <$> getVideoInfo
+
+
 initAudio :: UIM ()
 #ifdef SOUND
 initAudio = do
@@ -783,6 +803,14 @@
 #else
 initAudio = return ()
 #endif
+
+pollEvents = do
+    e <- pollEvent
+    case e of
+	NoEvent -> return []
+	_ -> do
+	    es <- pollEvents
+	    return $ e:es
 
 drawMsgLine = void.runMaybeT $ do
     (col,str) <- msum
diff --git a/SDLUIMInstance.hs b/SDLUIMInstance.hs
--- a/SDLUIMInstance.hs
+++ b/SDLUIMInstance.hs
@@ -270,18 +270,21 @@
     getUIBinding mode cmd = ($cmd) <$> getBindingStr mode
 
     initUI = liftM isJust (runMaybeT $ do
-	catchIOErrorMT $ SDL.init [InitVideo]
+	catchIOErrorMT $ SDL.init
+#ifdef SOUND
+            [InitVideo,InitAudio]
+#else
+            [InitVideo]
+#endif
 	catchIOErrorMT TTF.init
 	lift $ do
 	    readUIConfigFile
 	    initVideo 0 0
-	    liftIO $ setCaption "intricacy" "intricacy"
+	    liftIO $ initMisc
 	    w <- gets scrWidth
 	    h <- gets scrHeight
 	    liftIO $ warpMouse (fi $ w`div`2) (fi $ h`div`2)
 	    renderToMain $ erase
-	    liftIO $ enableUnicode True
-	    liftIO $ enableKeyRepeat 250 30
 	    initAudio
 	    readBindings
 	)
@@ -297,15 +300,22 @@
     suspend = return ()
     redraw = return ()
 
-    getDrawImpatience = do
-	curState <- get
-	let pos = serverWaitPos
-	return $ \ticks -> void $ flip runStateT curState $ do
-	    when (ticks>2) $ renderToMain $ do
-		mapM (drawAtRel (FilledHexGlyph $ bright black)) [ pos +^ i*^hu | i <- [0..3] ]
-		withFont (dispFontSmall curState) $
-		    renderStrColAtLeft errorCol ("waiting..."++replicate (ticks`mod`3) '.') $ pos
-	    refresh
+    impatience ticks = do
+	liftIO $ threadDelay 50000
+	if (ticks>20) then do
+		let pos = serverWaitPos
+		smallFont <- gets dispFontSmall
+		renderToMain $ do
+		    mapM (drawAtRel (FilledHexGlyph $ bright black)) [ pos +^ i*^hu | i <- [0..3] ]
+		    withFont smallFont $
+			renderStrColAtLeft errorCol ("waiting..."++replicate ((ticks`div`5)`mod`3) '.') $ pos
+		clearButtons
+		registerButton (pos +^ neg hv) CmdQuit 0 [("abort",hu+^neg hw)]
+		drawButtons IMImpatience
+		refresh
+		cmds <- getInput IMImpatience
+		return $ CmdQuit `elem` cmds
+	    else return False
 
     warpPointer pos = do
 	(scrCentre, size) <- getGeom
@@ -481,7 +491,7 @@
 	    processEvent (VideoResize w h) = do
 		initVideo w h
 		return [ CmdRedraw ]
-	    processEvent VideoExpose = return [ CmdRedraw ]
+	    processEvent VideoExpose = return [ CmdRefresh ]
 	    processEvent Quit = return [ CmdForceQuit ]
 
 	    processEvent _ = return []
@@ -640,14 +650,6 @@
     es' <- pollEvents
     return $ es++es'
 
-pollEvents = do
-    e <- pollEvent
-    case e of
-	NoEvent -> return []
-	_ -> do
-	    es <- pollEvents
-	    return $ e:es
-
 updateHoverStr :: InputMode -> UIM ()
 updateHoverStr mode = do
     p@(mPos,isCentral) <- gets mousePos
@@ -790,7 +792,7 @@
 
     size <- snd <$> lift getGeom
     lift $ do
-	renderToMain $ displaceRender (SVec size 0) $ renderStrColAt (brightish white) "PRIVY" $ accessedByPos +^ hv
+	renderToMain $ displaceRender (SVec size 0) $ renderStrColAt (brightish white) "UNLOCKED BY" $ accessedByPos +^ hv
 	registerSelectable (accessedByPos +^ hv) 0 SelPrivyHeader
 	registerSelectable (accessedByPos +^ hv +^ hu) 0 SelPrivyHeader
     if public lockinfo
@@ -833,7 +835,7 @@
 				renderToMain (drawAtRel (HollowGlyph $ dim green) pos)))
 
     lift $ do
-	renderToMain $ displaceRender (SVec size 0) $ renderStrColAt (brightish white) "NOTES" $ notesPos +^ hv
+	renderToMain $ displaceRender (SVec size 0) $ renderStrColAt (brightish white) "SECURING" $ notesPos +^ hv
 	registerSelectable (notesPos +^ hv) 0 SelNotesHeader
 	registerSelectable (notesPos +^ hv +^ hu) 0 SelNotesHeader
     if null $ notesSecured lockinfo
diff --git a/c_main.c b/c_main.c
new file mode 100644
--- /dev/null
+++ b/c_main.c
@@ -0,0 +1,46 @@
+/* Workaround for SDL on OS X, based on the corresponding file in TimePiece-0.0.5
+ *   http://hackage.haskell.org/package/TimePiece ,
+ * placed in the public domin by by Audrey Tang <audreyt@audreyt.org>. */
+#include <stdio.h>
+#include "HsFFI.h"
+
+#ifdef __APPLE__
+#include <objc/objc.h>
+#include <objc/objc-runtime.h>
+#endif
+
+#include <SDL.h>
+
+#ifdef __GLASGOW_HASKELL__
+#include "Main_stub.h"
+extern void __stginit_Main ( void );
+#endif
+
+#ifdef main
+int SDL_main(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+    int i;
+
+#ifdef __APPLE__
+    void * pool =
+      objc_msgSend(objc_lookUpClass("NSAutoreleasePool"), sel_getUid("alloc"));
+    objc_msgSend(pool, sel_getUid("init"));
+#endif
+
+    hs_init(&argc, &argv);
+#ifdef __GLASGOW_HASKELL__
+    hs_add_root(__stginit_Main);
+#endif
+
+    hs_MAIN();
+
+    hs_exit();
+
+#ifdef __APPLE__
+    objc_msgSend(pool, sel_getUid("release"));
+#endif
+    return 0;
+}
diff --git a/intricacy.cabal b/intricacy.cabal
--- a/intricacy.cabal
+++ b/intricacy.cabal
@@ -1,5 +1,5 @@
 name:                intricacy
-version:             0.5.5
+version:             0.5.7
 synopsis:            A game of competitive puzzle-design
 homepage:            http://mbays.freeshell.org/intricacy
 license:             GPL-3
@@ -69,9 +69,14 @@
               if flag(Sound)
                   Extra-Libraries: SDL_mixer
               ghc-options: -optl-mwindows
-      if os(darwin)
-          -- I'm told OSX doesn't like static builds
-          ghc-options: -dynamic
+          if os(darwin)
+              -- network-fancy-0.2.1+ causes a ___xpg_strerror_r link error
+              build-depends: network-fancy < 0.2.1
+              -- workaround for SDL on OS X, borrowed from TimePiece-0.0.5
+              cpp-options: -DAPPLE
+              ghc-options: -no-hs-main
+              include-dirs: /usr/include/SDL /usr/local/include/SDL
+              c-sources: c_main.c
   else
       Buildable: False
   if flag(Curses)
@@ -110,9 +115,6 @@
         , cryptohash >= 0.8
         , random >= 1.0, pipes >= 4
         , feed >= 0.3.1, xml >= 1.2.6
-      if os(darwin)
-          -- I'm told OSX doesn't like static builds
-          ghc-options: -dynamic
   else
     Buildable: False
   main-is:  Server.hs
diff --git a/tutorial/5-springs.lock b/tutorial/5-springs.lock
--- a/tutorial/5-springs.lock
+++ b/tutorial/5-springs.lock
@@ -6,7 +6,7 @@
    "                       # "   " 
   "   & &   % % %         # # " "  
  "     ]   " S S %           # "   
-"       ]   " # # # #   #     # "  
+"       ]   " # # #     #     # "  
  "       ]   " ] # #   # #   7 "   
   "       ] # " ] #   # #   7 "    
  " "     # # # " " # # S S % "     
