diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-hfsnotify
+hfsnotify [![Linux and Mac build Status](https://travis-ci.org/codedownio/hfsnotify.svg)](https://travis-ci.org/codedownio/hfsnotify) [![Windows build status](https://ci.appveyor.com/api/projects/status/u47y5jexvn4lix3c?svg=true)](https://ci.appveyor.com/project/thomasjm/hfsnotify)
 =========
 
 Unified Haskell interface for basic file system notifications.
diff --git a/fsnotify.cabal b/fsnotify.cabal
--- a/fsnotify.cabal
+++ b/fsnotify.cabal
@@ -1,7 +1,7 @@
 Name:                   fsnotify
-Version:                0.2.1.2
+Version:                0.3.0.0
 Author:                 Mark Dittmer <mark.s.dittmer@gmail.com>
-Maintainer:             Greg Weber <greg@gregweber.info>, Roman Cheplyaka <roma@ro-che.info>
+Maintainer:             Tom McLaughlin <tom@codedown.io>
 License:                BSD3
 License-File:           LICENSE
 Synopsis:               Cross platform library for file change notification.
@@ -16,7 +16,7 @@
 Extra-Source-Files:
   README.md
   CHANGELOG.md
-  test/test.hs
+  test/Test.hs
   test/EventUtils.hs
 
 
@@ -41,7 +41,9 @@
   if os(linux)
     CPP-Options:        -DOS_Linux
     Other-Modules:      System.FSNotify.Linux
-    Build-Depends:      hinotify >= 0.3.10
+    Build-Depends:      hinotify >= 0.3.0,
+                        shelly >= 1.6.5,
+                        unix >= 2.7.1.0
   else
     if os(windows)
       CPP-Options:      -DOS_Win32
@@ -55,19 +57,15 @@
 
 Test-Suite test
   Type:                 exitcode-stdio-1.0
-  Main-Is:              test.hs
+  Main-Is:              Test.hs
   Other-modules:        EventUtils
   Hs-Source-Dirs:       test
   GHC-Options:          -Wall -threaded
-  Build-depends:          base >= 4.3.1.0
-                        , tasty >= 0.5
-                        , tasty-hunit
-                        , directory
-                        , filepath
-                        , unix-compat
-                        , fsnotify
-                        , async >= 2
-                        , temporary
+
+  if os(windows)
+    Build-Depends:      base >= 4.3.1.0, tasty >= 0.5, tasty-hunit, directory, filepath, unix-compat, fsnotify, async >= 2, temporary, random, Win32
+  else
+    Build-Depends:      base >= 4.3.1.0, tasty >= 0.5, tasty-hunit, directory, filepath, unix-compat, fsnotify, async >= 2, temporary, random
 
 Source-Repository head
   Type:                 git
diff --git a/src/System/FSNotify.hs b/src/System/FSNotify.hs
--- a/src/System/FSNotify.hs
+++ b/src/System/FSNotify.hs
@@ -33,6 +33,7 @@
        -- * Events
          Event(..)
        , EventChannel
+       , eventIsDirectory
        , eventTime
        , eventPath
        , Action
@@ -60,15 +61,14 @@
 
 import Prelude hiding (FilePath)
 
-import Data.Maybe
 import Control.Concurrent
 import Control.Concurrent.Async
 import Control.Exception
-import Control.Applicative
 import Control.Monad
-import System.FilePath
+import Data.Maybe
 import System.FSNotify.Polling
 import System.FSNotify.Types
+import System.FilePath
 
 import System.FSNotify.Listener (StopListening)
 
@@ -172,15 +172,13 @@
 -- | Watch the immediate contents of a directory by committing an Action for each event.
 -- Watching the immediate contents of a directory will only report events
 -- associated with files within the specified directory, and not files
--- within its subdirectories. No two events pertaining to the same FilePath will
--- be executed concurrently.
+-- within its subdirectories.
 watchDir :: WatchManager -> FilePath -> ActionPredicate -> Action -> IO StopListening
 watchDir wm = threadChan listen wm
 
 -- | Watch all the contents of a directory by committing an Action for each event.
 -- Watching all the contents of a directory will report events associated with
--- files within the specified directory and its subdirectories. No two events
--- pertaining to the same FilePath will be executed concurrently.
+-- files within the specified directory and its subdirectories.
 watchTree :: WatchManager -> FilePath -> ActionPredicate -> Action -> IO StopListening
 watchTree wm = threadChan listenRecursive wm
 
@@ -190,8 +188,7 @@
       -- (^ this is the type of listen and listenRecursive)
   ->  WatchManager -> FilePath -> ActionPredicate -> Action -> IO StopListening
 threadChan listenFn (WatchManager db listener cleanupVar) path actPred action =
-  modifyMVar cleanupVar $ \mbCleanup ->
-  case mbCleanup of
+  modifyMVar cleanupVar $ \mbCleanup -> case mbCleanup of
     -- check if we've been stopped
     Nothing -> return (Nothing, return ()) -- or throw an exception?
     Just cleanup -> do
diff --git a/src/System/FSNotify/Devel.hs b/src/System/FSNotify/Devel.hs
--- a/src/System/FSNotify/Devel.hs
+++ b/src/System/FSNotify/Devel.hs
@@ -19,12 +19,11 @@
     allEvents, existsEvents
   ) where
 
-import Prelude hiding (FilePath)
-
 import Data.Text
-import System.FilePath
+import Prelude hiding (FilePath)
 import System.FSNotify
 import System.FSNotify.Path (hasThisExtension)
+import System.FilePath
 
 -- | In the given directory tree, watch for any 'Added' and 'Modified'
 -- events (but ignore 'Removed' events) for files with the given file
@@ -52,24 +51,27 @@
 doAllEvents :: Monad m => (FilePath -> m ()) -> Event -> m ()
 doAllEvents action event =
   case event of
-    Added    f _ -> action f
-    Modified f _ -> action f
-    Removed  f _ -> action f
+    Added    f _ _ -> action f
+    Modified f _ _ -> action f
+    Removed  f _ _ -> action f
+    Unknown  f _ _ -> action f
 
 -- | Turn a 'FilePath' predicate into an 'Event' predicate that accepts
 -- only 'Added' and 'Modified' event types
 existsEvents :: (FilePath -> Bool) -> (Event -> Bool)
 existsEvents filt event =
   case event of
-    Added    f _ -> filt f
-    Modified f _ -> filt f
-    Removed  _ _ -> False
+    Added    f _ _ -> filt f
+    Modified f _ _ -> filt f
+    Removed  _ _ _ -> False
+    Unknown  _ _ _ -> False
 
 -- | Turn a 'FilePath' predicate into an 'Event' predicate that accepts
 -- any event types
 allEvents :: (FilePath -> Bool) -> (Event -> Bool)
 allEvents filt event =
   case event of
-    Added    f _ -> filt f
-    Modified f _ -> filt f
-    Removed  f _ -> filt f
+    Added    f _ _ -> filt f
+    Modified f _ _ -> filt f
+    Removed  f _ _ -> filt f
+    Unknown  f _ _ -> filt f
diff --git a/src/System/FSNotify/Linux.hs b/src/System/FSNotify/Linux.hs
--- a/src/System/FSNotify/Linux.hs
+++ b/src/System/FSNotify/Linux.hs
@@ -2,8 +2,7 @@
 -- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
 --
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE CPP, DeriveDataTypeable, ScopedTypeVariables #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module System.FSNotify.Linux
@@ -11,31 +10,34 @@
        , NativeManager
        ) where
 
-import Prelude hiding (FilePath)
-
-
 import Control.Concurrent.Chan
 import Control.Concurrent.MVar
 import Control.Exception as E
-import Control.Monad (when)
+import Control.Monad
 import qualified Data.ByteString as BS
 import Data.IORef (atomicModifyIORef, readIORef)
+import Data.String
+import qualified Data.Text as T
 import Data.Time.Clock (UTCTime, getCurrentTime)
+import Data.Time.Clock.POSIX
 import Data.Typeable
--- import Debug.Trace (trace)
 import qualified GHC.Foreign as F
 import GHC.IO.Encoding (getFileSystemEncoding)
-import System.FilePath
+import Prelude hiding (FilePath)
+import qualified Shelly as S
 import System.FSNotify.Listener
 import System.FSNotify.Path (findDirs, canonicalizeDirPath)
 import System.FSNotify.Types
+import System.FilePath
 import qualified System.INotify as INo
+import System.Posix.Files (getFileStatus, isDirectory, modificationTimeHiRes)
 
 type NativeManager = INo.INotify
 
 data EventVarietyMismatchException = EventVarietyMismatchException deriving (Show, Typeable)
 instance Exception EventVarietyMismatchException
 
+#if MIN_VERSION_hinotify(0, 3, 10)
 toRawFilePath :: FilePath -> IO BS.ByteString
 toRawFilePath fp = do
   enc <- getFileSystemEncoding
@@ -45,52 +47,40 @@
 fromRawFilePath bs = do
   enc <- getFileSystemEncoding
   BS.useAsCString bs (F.peekCString enc)
+#else
+toRawFilePath = return . id
+fromRawFilePath = return . id
+#endif
 
--- Note that INo.Closed in this context is "modified" because we listen to
--- CloseWrite events.
-fsnEvent :: FilePath -> UTCTime -> INo.Event -> IO (Maybe Event)
-fsnEvent basePath timestamp event = case event of
-  INo.Created  False       raw    -> do
-    name <- fromRawFilePath raw
-    return $ Just (Added    (basePath </> name) timestamp)
-  INo.Closed   False (Just raw) _ -> do
-    name <- fromRawFilePath raw
-    return $ Just (Modified (basePath </> name) timestamp)
-  INo.MovedOut False       raw  _ -> do
-    name <- fromRawFilePath raw
-    return $ Just (Removed  (basePath </> name) timestamp)
-  INo.MovedIn  False       raw  _ -> do
-    name <- fromRawFilePath raw
-    return $ Just (Added    (basePath </> name) timestamp)
-  INo.Deleted  False       raw    -> do
-    name <- fromRawFilePath raw
-    return $ Just (Removed  (basePath </> name) timestamp)
-  _                               ->
-    return Nothing
+fsnEvents :: FilePath -> UTCTime -> INo.Event -> IO [Event]
+fsnEvents basePath timestamp (INo.Attributes isDir (Just raw)) = fromRawFilePath raw >>= \name -> return [Modified (basePath </> name) timestamp isDir]
+fsnEvents basePath timestamp (INo.Modified isDir (Just raw)) = fromRawFilePath raw >>= \name -> return [Modified (basePath </> name) timestamp isDir]
+fsnEvents basePath timestamp (INo.Created isDir raw) = fromRawFilePath raw >>= \name -> return [Added (basePath </> name) timestamp isDir]
+fsnEvents basePath timestamp (INo.MovedOut isDir raw _cookie) = fromRawFilePath raw >>= \name -> return [Removed (basePath </> name) timestamp isDir]
+fsnEvents basePath timestamp (INo.MovedIn isDir raw _cookie) = fromRawFilePath raw >>= \name -> return [Added (basePath </> name) timestamp isDir]
+fsnEvents basePath timestamp (INo.Deleted isDir raw) = fromRawFilePath raw >>= \name -> return [Removed (basePath </> name) timestamp isDir]
+fsnEvents _ _ (INo.Ignored) = return []
+fsnEvents basePath timestamp inoEvent = return [Unknown basePath timestamp (show inoEvent)]
 
 handleInoEvent :: ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> INo.Event -> IO ()
--- handleInoEvent _       _    basePath _   inoEvent | trace ("Linux: handleInoEvent " ++ show basePath ++ " " ++ show inoEvent) False = undefined
 handleInoEvent actPred chan basePath dbp inoEvent = do
   currentTime <- getCurrentTime
-  maybeFsnEvent <- fsnEvent basePath currentTime inoEvent
-  handleEvent actPred chan dbp maybeFsnEvent
+  events <- fsnEvents basePath currentTime inoEvent
+  mapM_ (handleEvent actPred chan dbp) events
 
-handleEvent :: ActionPredicate -> EventChannel -> DebouncePayload -> Maybe Event -> IO ()
--- handleEvent actPred _    _   (Just event) | trace ("Linux: handleEvent " ++ show (actPred event) ++ " " ++ show event) False = undefined
-handleEvent actPred chan dbp (Just event) =
+handleEvent :: ActionPredicate -> EventChannel -> DebouncePayload -> Event -> IO ()
+handleEvent actPred chan dbp event =
   when (actPred event) $ case dbp of
     (Just (DebounceData epsilon ior)) -> do
       lastEvent <- readIORef ior
-      when (not $ debounce epsilon lastEvent event) writeToChan
-      atomicModifyIORef ior (\_ -> (event, ()))
-    Nothing                           -> writeToChan
+      unless (debounce epsilon lastEvent event) writeToChan
+      atomicModifyIORef ior (const (event, ()))
+    Nothing -> writeToChan
   where
     writeToChan = writeChan chan event
--- handleEvent _ _ _ Nothing | trace ("Linux handleEvent Nothing") False = undefined
-handleEvent _ _ _ Nothing = return ()
 
 varieties :: [INo.EventVariety]
-varieties = [INo.Create, INo.Delete, INo.MoveIn, INo.MoveOut, INo.CloseWrite]
+varieties = [INo.Create, INo.Delete, INo.MoveIn, INo.MoveOut, INo.Attrib, INo.Modify]
 
 instance FileListener INo.INotify where
   initSession = E.catch (fmap Just INo.initINotify) (\(_ :: IOException) -> return Nothing)
@@ -120,7 +110,7 @@
     let
       stopListening = do
         modifyMVar_ wdVar $ \mbWds -> do
-          maybe (return ()) (mapM_ INo.removeWatch) mbWds
+          maybe (return ()) (mapM_ (\x -> catch (INo.removeWatch x) (\(_ :: SomeException) -> putStrLn ("Error removing watch: " `mappend` show x)))) mbWds
           return Nothing
 
     listenRec initialPath wdVar
@@ -149,10 +139,42 @@
               return $ Just (wd:wds)
         where
           handler :: FilePath -> DebouncePayload -> INo.Event -> IO ()
-          handler baseDir _   (INo.Created True rawDirPath) = do
-            dirPath <- fromRawFilePath rawDirPath
-            listenRec (baseDir </> dirPath) wdVar
-          handler baseDir dbp event                      =
+          handler baseDir dbp event = do
+            -- When a new directory is created, add recursive inotify watches to it
+            -- TODO: there's a race condition here; if there are files present in the directory before
+            -- we add the watches, we'll miss them. The right thing to do would be to ls the directory
+            -- and trigger Added events for everything we find there
+            case event of
+              (INo.Created True rawDirPath) -> do
+                dirPath <- fromRawFilePath rawDirPath
+                let newDir = baseDir </> dirPath
+                timestampBeforeAddingWatch <- getPOSIXTime
+                listenRec newDir wdVar
+
+                -- Find all files/folders that might have been created *after* the timestamp, and hence might have been
+                -- missed by the watch
+                -- TODO: there's a chance of this generating double events, fix
+                files <- S.shelly $ S.find (fromString newDir)
+                forM_ files $ \file -> do
+                  let newPath = T.unpack $ S.toTextIgnore file
+                  fileStatus <- getFileStatus newPath
+                  let modTime = modificationTimeHiRes fileStatus
+                  when (modTime > timestampBeforeAddingWatch) $ do
+                    handleEvent actPred chan dbp (Added (newDir </> newPath) (posixSecondsToUTCTime timestampBeforeAddingWatch) (isDirectory fileStatus))
+
+              _ -> return ()
+
+            -- Remove watch when this directory is removed
+            case event of
+              (INo.DeletedSelf) -> do
+                -- putStrLn "Watched file/folder was deleted! TODO: remove watch."
+                return ()
+              (INo.Ignored) -> do
+                -- putStrLn "Watched file/folder was ignored, which possibly means it was deleted. TODO: remove watch."
+                return ()
+              _ -> return ()
+
+            -- Forward all events, including directory create
             handleInoEvent actPred chan baseDir dbp event
 
   usesPolling = const False
diff --git a/src/System/FSNotify/Listener.hs b/src/System/FSNotify/Listener.hs
--- a/src/System/FSNotify/Listener.hs
+++ b/src/System/FSNotify/Listener.hs
@@ -11,13 +11,12 @@
        , newDebouncePayload
        ) where
 
-import Prelude hiding (FilePath)
-
 import Data.IORef (newIORef)
 import Data.Time (diffUTCTime, NominalDiffTime)
 import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
-import System.FilePath
+import Prelude hiding (FilePath)
 import System.FSNotify.Types
+import System.FilePath
 
 -- | An action that cancels a watching/listening job
 type StopListening = IO ()
@@ -57,7 +56,7 @@
 
 -- | The default event that provides a basis for comparison.
 eventDefault :: Event
-eventDefault = Added "" (posixSecondsToUTCTime 0)
+eventDefault = Added "" (posixSecondsToUTCTime 0) False
 
 -- | A predicate indicating whether two events may be considered "the same
 -- event". This predicate is applied to the most recent dispatched event and
diff --git a/src/System/FSNotify/OSX.hs b/src/System/FSNotify/OSX.hs
--- a/src/System/FSNotify/OSX.hs
+++ b/src/System/FSNotify/OSX.hs
@@ -3,33 +3,34 @@
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
 --
 
+{-# LANGUAGE MultiWayIf #-}
+
 module System.FSNotify.OSX
        ( FileListener(..)
        , NativeManager
        ) where
 
-import Prelude hiding (FilePath)
-
 import Control.Concurrent.Chan
 import Control.Concurrent.MVar
 import Control.Monad
 import Data.Bits
 import Data.IORef (atomicModifyIORef, readIORef)
 import Data.Map (Map)
+import qualified Data.Map as Map
 import Data.Time.Clock (UTCTime, getCurrentTime)
-import Data.Word
 import Data.Unique
-import System.FilePath
+import Data.Word
+import Prelude hiding (FilePath)
 import System.Directory
 import System.FSNotify.Listener
 import System.FSNotify.Path (canonicalizeDirPath)
 import System.FSNotify.Types
-import qualified Data.Map as Map
+import System.FilePath
 import qualified System.OSX.FSEvents as FSE
 
-data ListenType = NonRecursive | Recursive
-data WatchData = WatchData FSE.EventStream ListenType EventChannel
 
+data WatchData = WatchData FSE.EventStream EventChannel
+
 type WatchMap = Map Unique WatchData
 data OSXManager = OSXManager (MVar WatchMap)
 type NativeManager = OSXManager
@@ -47,74 +48,83 @@
     dirFlag = FSE.eventFlagItemIsDir
     path = FSE.eventPath event
 
+-- We have to be careful about interpreting the flags in a given event, because
+-- "really it's an OR of all the changes made since the FSEventsListener is created"
+-- See https://stackoverflow.com/questions/18415285/osx-fseventstreameventflags-not-working-correctly
+-- Thus, we try to look at whether the path exists or not to determine whether it was created, modified, etc.
+
+-- Note that there's still some bugs possible due to event coalescing, which the docs say is a possibility:
+-- for example, a file could be created and modified within a short time interval, and then we'd only emit one
+-- event (the "modified" one, given the logic below)
+-- See https://developer.apple.com/library/content/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html
 fsnEvents :: UTCTime -> FSE.Event -> IO [Event]
-fsnEvents timestamp fseEvent = liftM concat . sequence $ map (\f -> f fseEvent) (eventFunctions timestamp)
+fsnEvents timestamp e = do
+  -- Note: we *don't* want to use the canonical event path in the existence check, because of the aforementioned crazy event coalescing.
+  -- For example, suppose a directory is created and deleted, and then a file is created with the same name. This means the isDirectory flag might
+  -- still be turned on, which could lead us to construct a canonical event path with a trailing slash, which would then cause the existence
+  -- check to fail and make us think the file was removed.
+  -- The upshot of this is that the canonical event paths in the events we emit can't really be trusted, but hey, that's what the extra flag
+  -- on the event is for :(
+  exists <- doesPathExist $ FSE.eventPath e
+
+  -- Uncomment for an easy way to see flag activity when testing manually
+  -- putStrLn $ show ["Event", show e, "isDirectory", show isDirectory, "isFile", show isFile, "isModified", show isModified, "isCreated", show isCreated, "path", path e, "exists", show exists]
+
+  return $ if | exists && isModified -> [Modified (path e) timestamp isDirectory]
+              | exists && isCreated -> [Added (path e) timestamp isDirectory]
+              | (not exists) && hasFlag e FSE.eventFlagItemRemoved -> [Removed (path e) timestamp isDirectory]
+
+              -- Rename stuff
+              | exists && isRenamed -> [Added (path e) timestamp isDirectory]
+              | (not exists) && isRenamed -> [Removed (path e) timestamp isDirectory]
+
+              | otherwise -> []
   where
-    eventFunctions :: UTCTime -> [FSE.Event -> IO [Event]]
-    eventFunctions t = [addedFn t, modifFn t, removFn t, renamFn t]
-    addedFn t e = if hasFlag e FSE.eventFlagItemCreated        then return [Added    (path e) t] else return []
-    modifFn t e = if (hasFlag e FSE.eventFlagItemModified
-                   || hasFlag e FSE.eventFlagItemInodeMetaMod) then return [Modified (path e) t] else return []
-    removFn t e = if hasFlag e FSE.eventFlagItemRemoved        then return [Removed  (path e) t] else return []
-    renamFn t e = if hasFlag e FSE.eventFlagItemRenamed then
-                    doesFileExist (path e) >>= \exists -> if exists   then return [Added    (path e) t] else return [Removed (path e) t]
-                  else
-                    return []
+    isDirectory = hasFlag e FSE.eventFlagItemIsDir
+    isFile = hasFlag e FSE.eventFlagItemIsFile
+    isCreated = hasFlag e FSE.eventFlagItemCreated
+    isRenamed = hasFlag e FSE.eventFlagItemRenamed
+    isModified = hasFlag e FSE.eventFlagItemModified || hasFlag e FSE.eventFlagItemInodeMetaMod
     path = canonicalEventPath
     hasFlag event flag = FSE.eventFlags event .&. flag /= 0
 
--- Separate logic is needed for non-recursive events in OSX because the
--- hfsevents package doesn't support non-recursive event reporting.
-
-handleNonRecursiveFSEEvent :: ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> FSE.Event -> IO ()
-handleNonRecursiveFSEEvent actPred chan dirPath dbp fseEvent = do
+handleEvent :: Bool -> ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> FSE.Event -> IO ()
+handleEvent isRecursive actPred chan dirPath dbp fseEvent = do
   currentTime <- getCurrentTime
   events <- fsnEvents currentTime fseEvent
-  handleNonRecursiveEvents actPred chan dirPath dbp events
-handleNonRecursiveEvents :: ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> [Event] -> IO ()
-handleNonRecursiveEvents actPred chan dirPath dbp (event:events)
-  | takeDirectory dirPath == takeDirectory (eventPath event) && actPred event = do
-    case dbp of
-      (Just (DebounceData epsilon ior)) -> do
-        lastEvent <- readIORef ior
-        when (not $ debounce epsilon lastEvent event) (writeChan chan event)
-        atomicModifyIORef ior (\_ -> (event, ()))
-      Nothing                           -> writeChan chan event
-    handleNonRecursiveEvents actPred chan dirPath dbp events
-  | otherwise                                                         = handleNonRecursiveEvents actPred chan dirPath dbp events
-handleNonRecursiveEvents _ _ _ _ []                                   = return ()
+  handleEvents isRecursive actPred chan dirPath dbp events
 
-handleFSEEvent :: ActionPredicate -> EventChannel -> DebouncePayload -> FSE.Event -> IO ()
-handleFSEEvent actPred chan dbp fseEvent = do
-  currentTime <- getCurrentTime
-  events <- fsnEvents currentTime fseEvent
-  handleEvents actPred chan dbp events
+-- | For non-recursive monitoring, test if an event takes place directly inside the monitored folder
+isDirectlyInside :: FilePath -> Event -> Bool
+isDirectlyInside dirPath event = isRelevantFileEvent || isRelevantDirEvent
+  where
+    isRelevantFileEvent = (not $ eventIsDirectory event) && (takeDirectory dirPath == (takeDirectory $ eventPath event))
+    isRelevantDirEvent = eventIsDirectory event && (takeDirectory dirPath == (takeDirectory $ takeDirectory $ eventPath event))
 
-handleEvents :: ActionPredicate -> EventChannel -> DebouncePayload -> [Event] -> IO ()
-handleEvents actPred chan dbp (event:events) = do
-  when (actPred event) $ case dbp of
+handleEvents :: Bool -> ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> [Event] -> IO ()
+handleEvents isRecursive actPred chan dirPath dbp (event:events) = do
+  when (actPred event && (isRecursive || (isDirectlyInside dirPath event))) $ case dbp of
       (Just (DebounceData epsilon ior)) -> do
         lastEvent <- readIORef ior
         when (not $ debounce epsilon lastEvent event) (writeChan chan event)
         atomicModifyIORef ior (\_ -> (event, ()))
-      Nothing                           -> writeChan chan event
-  handleEvents actPred chan dbp events
-handleEvents _ _ _ [] = return ()
+      Nothing -> writeChan chan event
+  handleEvents isRecursive actPred chan dirPath dbp events
+handleEvents _ _ _ _ _ [] = return ()
 
-listenFn
-  :: (ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> FSE.Event -> IO a)
-  -> WatchConfig
-  -> OSXManager
-  -> FilePath
-  -> ActionPredicate
-  -> EventChannel
-  -> IO StopListening
+listenFn :: (ActionPredicate -> EventChannel -> FilePath -> DebouncePayload -> FSE.Event -> IO a)
+         -> WatchConfig
+         -> OSXManager
+         -> FilePath
+         -> ActionPredicate
+         -> EventChannel
+         -> IO StopListening
 listenFn handler conf (OSXManager mvarMap) path actPred chan = do
   path' <- canonicalizeDirPath path
   dbp <- newDebouncePayload $ confDebounce conf
   unique <- newUnique
   eventStream <- FSE.eventStreamCreate [path'] 0.0 True False True (handler actPred chan path' dbp)
-  modifyMVar_ mvarMap $ \watchMap -> return (Map.insert unique (WatchData eventStream NonRecursive chan) watchMap)
+  modifyMVar_ mvarMap $ \watchMap -> return (Map.insert unique (WatchData eventStream chan) watchMap)
   return $ do
     FSE.eventStreamDestroy eventStream
     modifyMVar_ mvarMap $ \watchMap -> return $ Map.delete unique watchMap
@@ -130,10 +140,9 @@
     forM_ (Map.elems watchMap) eventStreamDestroy'
     where
       eventStreamDestroy' :: WatchData -> IO ()
-      eventStreamDestroy' (WatchData eventStream _ _) = FSE.eventStreamDestroy eventStream
-
-  listen = listenFn handleNonRecursiveFSEEvent
+      eventStreamDestroy' (WatchData eventStream _) = FSE.eventStreamDestroy eventStream
 
-  listenRecursive = listenFn $ \actPred chan _ -> handleFSEEvent actPred chan
+  listen = listenFn $ handleEvent False
+  listenRecursive = listenFn $ handleEvent True
 
   usesPolling = const False
diff --git a/src/System/FSNotify/Path.hs b/src/System/FSNotify/Path.hs
--- a/src/System/FSNotify/Path.hs
+++ b/src/System/FSNotify/Path.hs
@@ -7,31 +7,29 @@
 module System.FSNotify.Path
        ( findFiles
        , findDirs
+       , findFilesAndDirs
        , canonicalizeDirPath
        , canonicalizePath
        , hasThisExtension
        ) where
 
-import Prelude hiding (FilePath)
-
-import Control.Applicative
 import Control.Monad
--- import Filesystem
--- import Filesystem.Path hiding (concat)
-
 import qualified Data.Text as T
+import Prelude hiding (FilePath)
 import qualified System.Directory as D
-import System.PosixCompat.Files as PF
 import System.FilePath
+import System.PosixCompat.Files as PF
 
 getDirectoryContentsPath :: FilePath -> IO [FilePath]
-getDirectoryContentsPath path = (map (path </>)) . filter (not . dots) <$> D.getDirectoryContents path
+getDirectoryContentsPath path =
+  ((map (path </>)) . filter (not . dots) <$> D.getDirectoryContents path) >>= filterM exists
   where
+    exists x = (||) <$> D.doesFileExist x <*> D.doesDirectoryExist x
     dots "."  = True
     dots ".." = True
     dots _    = False
 
-fileDirContents :: FilePath -> IO ([FilePath],[FilePath])
+fileDirContents :: FilePath -> IO ([FilePath], [FilePath])
 fileDirContents path = do
   contents <- getDirectoryContentsPath path
   stats <- mapM getFileStatus contents
@@ -56,6 +54,8 @@
   nestedDirs <- mapM findAllDirs dirs
   return (dirs ++ concat nestedDirs)
 
+-- * Exported functions below this point
+
 findFiles :: Bool -> FilePath -> IO [FilePath]
 findFiles True path  = findAllFiles       =<< canonicalizeDirPath path
 findFiles False path = findImmediateFiles =<<  canonicalizeDirPath path
@@ -63,6 +63,13 @@
 findDirs :: Bool -> FilePath -> IO [FilePath]
 findDirs True path  = findAllDirs       =<< canonicalizeDirPath path
 findDirs False path = findImmediateDirs =<< canonicalizeDirPath path
+
+findFilesAndDirs :: Bool -> FilePath -> IO [FilePath]
+findFilesAndDirs False path = getDirectoryContentsPath =<< canonicalizeDirPath path
+findFilesAndDirs True path = do
+  (files, dirs) <- fileDirContents path
+  nestedFilesAndDirs <- concat <$> (mapM (findFilesAndDirs False) dirs)
+  return (files ++ dirs ++ nestedFilesAndDirs)
 
 -- | add a trailing slash to ensure the path indicates a directory
 addTrailingSlash :: FilePath -> FilePath
diff --git a/src/System/FSNotify/Polling.hs b/src/System/FSNotify/Polling.hs
--- a/src/System/FSNotify/Polling.hs
+++ b/src/System/FSNotify/Polling.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ScopedTypeVariables #-}
 --
 -- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
@@ -9,90 +10,98 @@
   , FileListener(..)
   ) where
 
-import Prelude hiding (FilePath)
-
-import Control.Applicative
 import Control.Concurrent
+import Control.Exception
+import Control.Monad (forM_)
 import Data.Map (Map)
+import qualified Data.Map as Map
 import Data.Maybe
 import Data.Time.Clock (UTCTime, getCurrentTime)
 import Data.Time.Clock.POSIX
--- import Debug.Trace (trace)
-import System.FilePath
+import Prelude hiding (FilePath)
+import System.Directory (doesDirectoryExist)
 import System.FSNotify.Listener
-import System.FSNotify.Path (findFiles, canonicalizeDirPath)
+import System.FSNotify.Path (findFilesAndDirs, canonicalizeDirPath)
 import System.FSNotify.Types
+import System.FilePath
 import System.PosixCompat.Files
 import System.PosixCompat.Types
-import qualified Data.Map as Map
-import Control.Monad (forM_)
 
-data EventType =
-    AddedEvent
-  | ModifiedEvent
-  | RemovedEvent
+data EventType = AddedEvent
+               | ModifiedEvent
+               | RemovedEvent
 
-data WatchKey = WatchKey ThreadId deriving (Eq, Ord)
+newtype WatchKey = WatchKey ThreadId deriving (Eq, Ord)
 data WatchData = WatchData FilePath EventChannel
 type WatchMap = Map WatchKey WatchData
-data PollManager = PollManager (MVar WatchMap)
+newtype PollManager = PollManager (MVar WatchMap)
 
-generateEvent :: UTCTime -> EventType -> FilePath -> Maybe Event
-generateEvent timestamp AddedEvent    filePath = Just (Added    filePath timestamp)
-generateEvent timestamp ModifiedEvent filePath = Just (Modified filePath timestamp)
-generateEvent timestamp RemovedEvent  filePath = Just (Removed  filePath timestamp)
+generateEvent :: UTCTime -> Bool -> EventType -> FilePath -> Maybe Event
+generateEvent timestamp isDir AddedEvent filePath = Just (Added filePath timestamp isDir)
+generateEvent timestamp isDir ModifiedEvent filePath = Just (Modified filePath timestamp isDir)
+generateEvent timestamp isDir RemovedEvent filePath = Just (Removed filePath timestamp isDir)
 
-generateEvents :: UTCTime -> EventType -> [FilePath] -> [Event]
-generateEvents timestamp eventType = mapMaybe (generateEvent timestamp eventType)
+generateEvents :: UTCTime -> EventType -> [(FilePath, Bool)] -> [Event]
+generateEvents timestamp eventType = mapMaybe (\(path, isDir) -> generateEvent timestamp isDir eventType path)
 
+-- | Do not return modified events for directories.
+-- These can arise when files are created inside subdirectories, resulting in the modification time
+-- of the directory being bumped. However, to increase consistency with the other FileListeners,
+-- we ignore these events.
 handleEvent :: EventChannel -> ActionPredicate -> Event -> IO ()
+handleEvent _ _ (Modified _ _ True) = return ()
 handleEvent chan actPred event
   | actPred event = writeChan chan event
-  | otherwise     = return ()
-
-pathModMap :: Bool -> FilePath -> IO (Map FilePath UTCTime)
-pathModMap True  path = findFiles True path >>= pathModMap'
-pathModMap False path = findFiles False path >>= pathModMap'
+  | otherwise = return ()
 
-pathModMap' :: [FilePath] -> IO (Map FilePath UTCTime)
-pathModMap' files = fmap Map.fromList $ mapM pathAndTime files
+pathModMap :: Bool -> FilePath -> IO (Map FilePath (UTCTime, Bool))
+pathModMap recursive path = findFilesAndDirs recursive path >>= pathModMap'
   where
-    pathAndTime :: FilePath -> IO (FilePath, UTCTime)
-    pathAndTime path = do
+    pathModMap' :: [FilePath] -> IO (Map FilePath (UTCTime, Bool))
+    pathModMap' files = (Map.fromList . catMaybes) <$> mapM pathAndInfo files
+
+    pathAndInfo :: FilePath -> IO (Maybe (FilePath, (UTCTime, Bool)))
+    pathAndInfo path = handle (\(_ :: IOException) -> return Nothing) $ do
       modTime <- getModificationTime path
-      return (path, modTime)
+      isDir <- doesDirectoryExist path
+      return $ Just (path, (modTime, isDir))
 
-pollPath :: Int -> Bool -> EventChannel -> FilePath -> ActionPredicate -> Map FilePath UTCTime -> IO ()
+pollPath :: Int -> Bool -> EventChannel -> FilePath -> ActionPredicate -> Map FilePath (UTCTime, Bool) -> IO ()
 pollPath interval recursive chan filePath actPred oldPathMap = do
   threadDelay interval
-  newPathMap  <- pathModMap recursive filePath
-  currentTime <- getCurrentTime
-  let deletedMap = Map.difference oldPathMap newPathMap
-      createdMap = Map.difference newPathMap oldPathMap
-      modifiedAndCreatedMap = Map.differenceWith modifiedDifference newPathMap oldPathMap
-      modifiedMap = Map.difference modifiedAndCreatedMap createdMap
-      generateEvents' = generateEvents currentTime
-  handleEvents $ generateEvents' AddedEvent    $ Map.keys createdMap
-  handleEvents $ generateEvents' ModifiedEvent $ Map.keys modifiedMap
-  handleEvents $ generateEvents' RemovedEvent  $ Map.keys deletedMap
-  pollPath' newPathMap
+  maybeNewPathMap <- handle (\(_ :: IOException) -> return Nothing) (Just <$> pathModMap recursive filePath)
+  case maybeNewPathMap of
+    -- Something went wrong while listing directories; we'll try again on the next poll
+    Nothing -> pollPath interval recursive chan filePath actPred oldPathMap
+
+    Just newPathMap -> do
+      currentTime <- getCurrentTime
+      let deletedMap = Map.difference oldPathMap newPathMap
+          createdMap = Map.difference newPathMap oldPathMap
+          modifiedAndCreatedMap = Map.differenceWith modifiedDifference newPathMap oldPathMap
+          modifiedMap = Map.difference modifiedAndCreatedMap createdMap
+          generateEvents' = generateEvents currentTime
+
+      handleEvents $ generateEvents' AddedEvent [(path, isDir) | (path, (_, isDir)) <- Map.toList createdMap]
+      handleEvents $ generateEvents' ModifiedEvent [(path, isDir) | (path, (_, isDir)) <- Map.toList modifiedMap]
+      handleEvents $ generateEvents' RemovedEvent [(path, isDir) | (path, (_, isDir)) <- Map.toList deletedMap]
+
+      pollPath interval recursive chan filePath actPred newPathMap
+
   where
-    modifiedDifference :: UTCTime -> UTCTime -> Maybe UTCTime
-    modifiedDifference newTime oldTime
-      | oldTime /= newTime = Just newTime
-      | otherwise            = Nothing
+    modifiedDifference :: (UTCTime, Bool) -> (UTCTime, Bool) -> Maybe (UTCTime, Bool)
+    modifiedDifference (newTime, isDir1) (oldTime, isDir2)
+      | oldTime /= newTime || isDir1 /= isDir2 = Just (newTime, isDir1)
+      | otherwise = Nothing
 
     handleEvents :: [Event] -> IO ()
     handleEvents = mapM_ (handleEvent chan actPred)
 
-    pollPath' :: Map FilePath UTCTime -> IO ()
-    pollPath' = pollPath interval recursive chan filePath actPred
 
-
--- Additional init funciton exported to allow startManager to unconditionally
+-- Additional init function exported to allow startManager to unconditionally
 -- create a poll manager as a fallback when other managers will not instantiate.
 createPollManager :: IO PollManager
-createPollManager = fmap PollManager $ newMVar Map.empty
+createPollManager = PollManager <$> newMVar Map.empty
 
 killWatchingThread :: WatchKey -> IO ()
 killWatchingThread (WatchKey threadId) = killThread threadId
@@ -104,6 +113,16 @@
     return $ Map.delete wk m
   return ()
 
+listen' :: Bool -> WatchConfig -> PollManager -> FilePath -> ActionPredicate -> EventChannel -> IO (IO ())
+listen' isRecursive conf (PollManager mvarMap) path actPred chan = do
+  path' <- canonicalizeDirPath path
+  pmMap <- pathModMap isRecursive path'
+  threadId <- forkIO $ pollPath (confPollInterval conf) isRecursive chan path' actPred pmMap
+  let wk = WatchKey threadId
+  modifyMVar_ mvarMap $ return . Map.insert wk (WatchData path' chan)
+  return $ killAndUnregister mvarMap wk
+
+
 instance FileListener PollManager where
   initSession = fmap Just createPollManager
 
@@ -111,28 +130,15 @@
     watchMap <- readMVar mvarMap
     forM_ (Map.keys watchMap) killWatchingThread
 
-  listen conf (PollManager mvarMap) path actPred chan  = do
-    path' <- canonicalizeDirPath path
-    pmMap <- pathModMap False path'
-    threadId <- forkIO $ pollPath (confPollInterval conf) False chan path' actPred pmMap
-    let wk = WatchKey threadId
-    modifyMVar_ mvarMap $ return . Map.insert wk (WatchData path' chan)
-    return $ killAndUnregister mvarMap wk
+  listen = listen' False
 
-  listenRecursive conf (PollManager mvarMap) path actPred chan = do
-    path' <- canonicalizeDirPath path
-    pmMap <- pathModMap True  path'
-    threadId <- forkIO $ pollPath (confPollInterval conf) True chan path' actPred pmMap
-    let wk = WatchKey threadId
-    modifyMVar_ mvarMap $ return . Map.insert wk (WatchData path' chan)
-    return $ killAndUnregister mvarMap wk
+  listenRecursive = listen' True
 
   usesPolling = const True
 
 
 getModificationTime :: FilePath -> IO UTCTime
 getModificationTime p = fromEpoch . modificationTime <$> getFileStatus p
-
 
 fromEpoch :: EpochTime -> UTCTime
 fromEpoch = posixSecondsToUTCTime . realToFrac
diff --git a/src/System/FSNotify/Types.hs b/src/System/FSNotify/Types.hs
--- a/src/System/FSNotify/Types.hs
+++ b/src/System/FSNotify/Types.hs
@@ -15,15 +15,15 @@
        , EventChannel
        , eventPath
        , eventTime
+       , eventIsDirectory
        , IOEvent
        ) where
 
-import Prelude hiding (FilePath)
-
 import Control.Concurrent.Chan
 import Data.IORef (IORef)
 import Data.Time (NominalDiffTime)
 import Data.Time.Clock (UTCTime)
+import Prelude hiding (FilePath)
 import System.FilePath
 
 -- | A file event reported by a file watcher. Each event contains the
@@ -31,22 +31,32 @@
 -- event occurred (timestamps represent current time when FSEvents receives
 -- it from the OS and/or platform-specific Haskell modules).
 data Event =
-    Added    FilePath UTCTime
-  | Modified FilePath UTCTime
-  | Removed  FilePath UTCTime
+    Added    FilePath UTCTime Bool
+  | Modified FilePath UTCTime Bool
+  | Removed  FilePath UTCTime Bool
+  | Unknown  FilePath UTCTime String
   deriving (Eq, Show)
 
 -- | Helper for extracting the path associated with an event.
 eventPath :: Event -> FilePath
-eventPath (Added    path _) = path
-eventPath (Modified path _) = path
-eventPath (Removed  path _) = path
+eventPath (Added    path _ _) = path
+eventPath (Modified path _ _) = path
+eventPath (Removed  path _ _) = path
+eventPath (Unknown  path _ _) = path
 
 -- | Helper for extracting the time associated with an event.
 eventTime :: Event -> UTCTime
-eventTime (Added    _ timestamp) = timestamp
-eventTime (Modified _ timestamp) = timestamp
-eventTime (Removed  _ timestamp) = timestamp
+eventTime (Added    _ timestamp _) = timestamp
+eventTime (Modified _ timestamp _) = timestamp
+eventTime (Removed  _ timestamp _) = timestamp
+eventTime (Unknown  _ timestamp _) = timestamp
+
+eventIsDirectory :: Event -> Bool
+eventIsDirectory (Added    _ _ isDir) = isDir
+eventIsDirectory (Modified _ _ isDir) = isDir
+eventIsDirectory (Removed  _ _ isDir) = isDir
+eventIsDirectory (Unknown  _ _ _) = False
+
 
 type EventChannel = Chan Event
 
diff --git a/src/System/FSNotify/Win32.hs b/src/System/FSNotify/Win32.hs
--- a/src/System/FSNotify/Win32.hs
+++ b/src/System/FSNotify/Win32.hs
@@ -9,12 +9,13 @@
        , NativeManager
        ) where
 
-import Prelude
-
-import Control.Concurrent.Chan
+import Control.Concurrent
 import Control.Monad (when)
+import Data.Bits
 import Data.IORef (atomicModifyIORef, readIORef)
+import qualified Data.Map as Map
 import Data.Time (getCurrentTime, UTCTime)
+import Prelude
 import System.FSNotify.Listener
 import System.FSNotify.Path (canonicalizeDirPath)
 import System.FSNotify.Types
@@ -32,36 +33,49 @@
 -- handle[native]Event.
 
 -- Win32-notify has (temporarily?) dropped support for Renamed events.
-fsnEvent :: BaseDir -> UTCTime -> WNo.Event -> Maybe Event
-fsnEvent basedir timestamp ev =
-  case ev of
-    WNo.Created  False name -> Just $ Added    (basedir </> name) timestamp
-    WNo.Modified False name -> Just $ Modified (basedir </> name) timestamp
-    WNo.Deleted  False name -> Just $ Removed  (basedir </> name) timestamp
-    _                       -> Nothing
-{-
-fsnEvents timestamp (WNo.Renamed  False (Just oldName) newName) = [Removed (fp oldName) timestamp, Added (fp newName) timestamp]
-fsnEvents timestamp (WNo.Renamed  False Nothing newName)        = [Added (fp newName) timestamp]
--}
+fsnEvent :: Bool -> BaseDir -> UTCTime -> WNo.Event -> Event
+fsnEvent isDirectory basedir timestamp (WNo.Created name) = Added (normalise (basedir </> name)) timestamp isDirectory
+fsnEvent isDirectory basedir timestamp (WNo.Modified name) = Modified (normalise (basedir </> name)) timestamp isDirectory
+fsnEvent isDirectory basedir timestamp (WNo.Deleted name) = Removed (normalise (basedir </> name)) timestamp isDirectory
 
-handleWNoEvent :: BaseDir -> ActionPredicate -> EventChannel -> DebouncePayload -> WNo.Event -> IO ()
-handleWNoEvent basedir actPred chan dbp inoEvent = do
+handleWNoEvent :: Bool -> BaseDir -> ActionPredicate -> EventChannel -> DebouncePayload -> WNo.Event -> IO ()
+handleWNoEvent isDirectory basedir actPred chan dbp inoEvent = do
   currentTime <- getCurrentTime
-  let maybeEvent = fsnEvent basedir currentTime inoEvent
-  case maybeEvent of
-    Just evt -> handleEvent actPred chan dbp evt
-    Nothing  -> return ()
+  let event = fsnEvent isDirectory basedir currentTime inoEvent
+  handleEvent actPred chan dbp event
+
 handleEvent :: ActionPredicate -> EventChannel -> DebouncePayload -> Event -> IO ()
-handleEvent actPred chan dbp event =
-  when (actPred event) $ case dbp of
+handleEvent actPred chan dbp event | actPred event = do
+  case dbp of
     (Just (DebounceData epsilon ior)) -> do
       lastEvent <- readIORef ior
-      when (not $ debounce epsilon lastEvent event) writeToChan
+      when (not $ debounce epsilon lastEvent event) $ writeChan chan event
       atomicModifyIORef ior (\_ -> (event, ()))
-    Nothing                           -> writeToChan
-  where
-    writeToChan = writeChan chan event
+    Nothing -> writeChan chan event
+handleEvent _ _ _ _ = return ()
 
+watchDirectory :: Bool -> WatchConfig -> WNo.WatchManager -> FilePath -> ActionPredicate -> EventChannel -> IO (IO ())
+watchDirectory isRecursive conf watchManager@(WNo.WatchManager mvarMap) path actPred chan = do
+  path' <- canonicalizeDirPath path
+  dbp <- newDebouncePayload $ confDebounce conf
+
+  let fileFlags = foldl (.|.) 0 [WNo.fILE_NOTIFY_CHANGE_FILE_NAME, WNo.fILE_NOTIFY_CHANGE_SIZE, WNo.fILE_NOTIFY_CHANGE_ATTRIBUTES]
+  let dirFlags = foldl (.|.) 0 [WNo.fILE_NOTIFY_CHANGE_DIR_NAME]
+
+  -- Start one watch for file events and one for directory events
+  -- (There seems to be no other way to provide isDirectory information)
+  wid1 <- WNo.watchDirectory watchManager path' isRecursive fileFlags (handleWNoEvent False path' actPred chan dbp)
+  wid2 <- WNo.watchDirectory watchManager path' isRecursive dirFlags (handleWNoEvent True path' actPred chan dbp)
+
+  -- The StopListening action should make sure to remove the watches from the manager after they're killed.
+  -- Otherwise, a call to killSession would cause us to try to kill them again, resulting in an invalid handle error.
+  return $ do
+    WNo.killWatch wid1
+    modifyMVar_ mvarMap $ \watchMap -> return (Map.delete wid1 watchMap)
+
+    WNo.killWatch wid2
+    modifyMVar_ mvarMap $ \watchMap -> return (Map.delete wid2 watchMap)
+
 instance FileListener WNo.WatchManager where
   -- TODO: This should actually lookup a Windows API version and possibly return
   -- Nothing the calls we need are not available. This will require that API
@@ -70,19 +84,7 @@
 
   killSession = WNo.killWatchManager
 
-  listen conf watchManager path actPred chan = do
-    path' <- canonicalizeDirPath path
-    dbp <- newDebouncePayload $ confDebounce conf
-    wid <- WNo.watchDirectory watchManager path' False varieties (handleWNoEvent path' actPred chan dbp)
-    return $ WNo.killWatch wid
-
-  listenRecursive conf watchManager path actPred chan = do
-    path' <- canonicalizeDirPath path
-    dbp <- newDebouncePayload $ confDebounce conf
-    wid <- WNo.watchDirectory watchManager path' True varieties (handleWNoEvent path' actPred chan dbp)
-    return $ WNo.killWatch wid
+  listen = watchDirectory False
+  listenRecursive = watchDirectory True
 
   usesPolling = const False
-
-varieties :: [WNo.EventVariety]
-varieties = [WNo.Create, WNo.Delete, WNo.Move, WNo.Modify]
diff --git a/test/EventUtils.hs b/test/EventUtils.hs
--- a/test/EventUtils.hs
+++ b/test/EventUtils.hs
@@ -1,19 +1,20 @@
 {-# LANGUAGE OverloadedStrings, ImplicitParams #-}
 module EventUtils where
 
-import Prelude hiding (FilePath)
-import Test.Tasty.HUnit
+import Control.Applicative
 import Control.Concurrent
 import Control.Concurrent.Async hiding (poll)
-import Control.Applicative
 import Control.Monad
 import Data.IORef
 import Data.List (sortBy)
+import Data.Monoid
 import Data.Ord (comparing)
-import System.FilePath
+import Prelude hiding (FilePath)
+import System.Directory
 import System.FSNotify
+import System.FilePath
 import System.IO.Unsafe
-import System.Directory
+import Test.Tasty.HUnit
 import Text.Printf
 
 delay :: (?timeInterval :: Int) => IO ()
@@ -26,23 +27,32 @@
   , patPredicate :: Event -> Bool
   }
 
-evAdded, evRemoved, evModified :: FilePath -> EventPattern
-evAdded path =
-  EventPattern
-    path
-    "Added"
-    (\x -> case x of Added path' _ -> path == path'; _ -> False)
-evRemoved path =
-  EventPattern
-    path
-    "Removed"
-    (\x -> case x of Removed path' _ -> path == path'; _ -> False)
-evModified path =
-  EventPattern
-    path
-    "Modified"
-    (\x -> case x of Modified path' _ -> path == path'; _ -> False)
+evAdded, evRemoved, evModified, evAddedOrModified :: Bool -> FilePath -> EventPattern
+evAdded isDirectory path = EventPattern path "Added"
+  (\x -> case x of
+      Added path' _ isDir | isDirectory == isDir -> pathMatches isDirectory path path'
+      _ -> False
+  )
+evRemoved isDirectory path = EventPattern path "Removed"
+  (\x -> case x of
+      Removed path' _ isDir | isDirectory == isDir -> pathMatches isDirectory path path'
+      _ -> False
+  )
+evModified isDirectory path = EventPattern path "Modified"
+  (\x ->
+     case x of
+       Modified path' _ isDir | isDirectory == isDir -> pathMatches isDirectory path path'
+       _ -> False
+  )
+evAddedOrModified isDirectory path = EventPattern path "AddedOrModified"
+  (\x -> case x of
+      Added path' _ isDir | isDirectory == isDir -> pathMatches isDirectory path path'
+      Modified path' _ isDir | isDirectory == isDir -> pathMatches isDirectory path path'
+      _ -> False
+  )
 
+pathMatches True path path' = path == path' || (path <> [pathSeparator]) == path'
+pathMatches False path path' = path == path'
 
 matchEvents :: [EventPattern] -> [Event] -> Assertion
 matchEvents expected actual = do
@@ -53,7 +63,7 @@
       (show actual)
   sequence_ $ (\f -> zipWith f expected actual) $ \pat ev ->
     assertBool
-      (printf "Unexpected event.\n  Expected :%s\n  Actual: %s\n"
+      (printf "Unexpected event.\n  Expected: %s\n  Actual: %s\n"
         (show expected)
         (show actual))
       (patPredicate pat ev)
@@ -69,11 +79,9 @@
   -> FilePath
   -> IO (Async [Event])
 gatherEvents poll watch path = do
-  mgr <- startManagerConf defaultConfig
-    { confDebounce = NoDebounce
-    , confUsePolling = poll
-    , confPollInterval = 2 * 10^(5 :: Int)
-    }
+  mgr <- startManagerConf defaultConfig { confDebounce = NoDebounce
+                                        , confUsePolling = poll
+                                        , confPollInterval = 2 * 10^(5 :: Int) }
   eventsVar <- newIORef []
   stop <- watch mgr path (const True) (\ev -> atomicModifyIORef eventsVar (\evs -> (ev:evs, ())))
   async $ do
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,128 @@
+{-# LANGUAGE CPP, OverloadedStrings, ImplicitParams, MultiWayIf #-}
+
+import Control.Concurrent
+import Control.Exception
+import Control.Monad
+import Data.Monoid
+import Prelude hiding (FilePath)
+import System.Directory
+import System.FSNotify
+import System.FilePath
+import System.IO
+import System.IO.Temp
+import System.PosixCompat.Files
+import System.Random as R
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import EventUtils
+
+#ifdef mingw32_HOST_OS
+import Data.Bits
+import System.Win32.File (getFileAttributes, setFileAttributes, fILE_ATTRIBUTE_TEMPORARY)
+-- Perturb the file's attributes, to check that a modification event is emitted
+changeFileAttributes :: FilePath -> IO ()
+changeFileAttributes file = do
+  attrs <- getFileAttributes file
+  setFileAttributes file (attrs `xor` fILE_ATTRIBUTE_TEMPORARY)
+#else
+changeFileAttributes :: FilePath -> IO ()
+changeFileAttributes = touchFile
+#endif
+
+
+isMac :: Bool
+#ifdef darwin_HOST_OS
+isMac = True
+#else
+isMac = False
+#endif
+
+nativeMgrSupported :: IO Bool
+nativeMgrSupported = do
+  mgr <- startManager
+  stopManager mgr
+  return $ not $ isPollingManager mgr
+
+main :: IO ()
+main = do
+  hasNative <- nativeMgrSupported
+  unless hasNative $ putStrLn "WARNING: native manager cannot be used or tested on this platform"
+  defaultMain $ withResource (createDirectoryIfMissing True testDirPath)
+                             (const $ removeDirectoryRecursive testDirPath)
+                             (const $ tests hasNative)
+
+-- | There's some kind of race in OS X where the creation of the containing directory shows up as an event
+-- I explored whether this was due to passing 0 as the sinceWhen argument to FSEventStreamCreate
+-- in the hfsevents package, but changing that didn't seem to help
+pauseBeforeStartingTest :: IO ()
+pauseBeforeStartingTest = threadDelay 10000
+
+tests :: Bool -> TestTree
+tests hasNative = testGroup "Tests" $ do
+  poll <- if hasNative then [False, True] else [True]
+  let ?timeInterval = if poll then 2*10^(6 :: Int) else 5*10^(5 :: Int)
+
+  return $ testGroup (if poll then "Polling" else "Native") $ do
+    recursive <- [False, True]
+    return $ testGroup (if recursive then "Recursive" else "Non-recursive") $ do
+      nested <- [False, True]
+
+      return $ testGroup (if nested then "In a subdirectory" else "Right here") $ do
+        t <- [ mkTest "new file" (if | isMac && not poll -> [evAddedOrModified False]
+                                     | otherwise -> [evAdded False])
+                                 (const $ return ())
+                                 (\f -> openFile f AppendMode >>= hClose)
+
+             , mkTest "modify file" [evModified False]
+                                    (\f -> writeFile f "")
+                                    (\f -> appendFile f "foo")
+
+             -- This test is disabled when polling because the PollManager only keeps track of
+             -- modification time, so it won't catch an unrelated file attribute change
+             , mkTest "modify file attributes" (if poll then [] else [evModified False])
+                                               (\f -> writeFile f "")
+                                               (\f -> if poll then return () else changeFileAttributes f)
+
+             , mkTest "delete file" [evRemoved False]
+                                    (\f -> writeFile f "")
+                                    (\f -> removeFile f)
+
+             , mkTest "new directory" (if | isMac -> [evAddedOrModified True]
+                                          | otherwise -> [evAdded True])
+                                      (const $ return ())
+                                      createDirectory
+
+             , mkTest "delete directory" [evRemoved True]
+                                         (\f -> createDirectory f)
+                                         removeDirectory
+          ]
+        return $ t nested recursive poll
+
+
+mkTest :: (?timeInterval::Int) => TestName -> [FilePath -> EventPattern] -> (FilePath -> IO a) ->
+          (FilePath -> IO ()) -> Bool -> Bool -> Bool -> TestTree
+mkTest title evs prepare action nested recursive poll = do
+  testCase title $ do
+    -- Use a random identifier so that every test happens in a different folder
+    -- This is unfortunately necessary because of the madness of OS X FSEvents; see the comments in OSX.hs
+    randomID <- replicateM 10 $ R.randomRIO ('a', 'z')
+
+    let pollDelay = when poll (threadDelay $ 10^(6 :: Int))
+
+    withTempDirectory testDirPath ("test." <> randomID) $ \watchedDir -> do
+      let fileName = "testfile"
+      let baseDir = if nested then watchedDir </> "subdir" else watchedDir
+          f = normalise $ baseDir </> fileName
+          watchFn = if recursive then watchTree else watchDir
+          expect = expectEvents poll watchFn watchedDir
+
+      createDirectoryIfMissing True baseDir
+
+      pauseBeforeStartingTest
+
+      flip finally (doesFileExist f >>= flip when (removeFile f)) $ do
+        _ <- prepare f
+        pauseBeforeStartingTest
+        flip expect (pollDelay >> action f) (if | nested && (not recursive) -> []
+                                                | otherwise -> [ev f | ev <- evs])
diff --git a/test/test.hs b/test/test.hs
deleted file mode 100644
--- a/test/test.hs
+++ /dev/null
@@ -1,88 +0,0 @@
-{-# LANGUAGE OverloadedStrings, ImplicitParams #-}
-import Prelude hiding
-  ( FilePath )
-import Control.Applicative
-import Test.Tasty
-import Test.Tasty.HUnit
-import System.Directory
-import System.FilePath
-import System.FSNotify
-import System.IO.Error
-import System.IO.Temp
-import System.PosixCompat.Files
-import Control.Monad
-import Control.Exception
-import Control.Concurrent
-
-import EventUtils
-
-nativeMgrSupported :: IO Bool
-nativeMgrSupported = do
-  mgr <- startManager
-  stopManager mgr
-  return $ not $ isPollingManager mgr
-
-main :: IO ()
-main = do
-  hasNative <- nativeMgrSupported
-  unless hasNative $
-    putStrLn "WARNING: native manager cannot be used or tested on this platform"
-  defaultMain $
-    withResource
-      (createDirectoryIfMissing True testDirPath)
-      (const $ removeDirectoryRecursive testDirPath) $
-      const $ tests hasNative
-
-tests :: Bool -> TestTree
-tests hasNative = testGroup "Tests" $ do
-  poll <-
-    if hasNative
-      then [False, True]
-      else [True]
-  let ?timeInterval =
-        if poll
-          then 2*10^(6 :: Int)
-          else 5*10^(5 :: Int)
-  return $ testGroup (if poll then "Polling" else "Native") $ do
-  recursive <- [False, True]
-  return $ testGroup (if recursive then "Recursive" else "Non-recursive") $ do
-  nested <- [False, True]
-  return $ testGroup (if nested then "In a subdirectory" else "Right here") $ do
-  t <-
-    [ mkTest "new file"
-        (if poll then [evAdded] else [evAdded, evModified])
-        (const $ return ())
-        (\f -> writeFile f "foo")
-    , mkTest "modify file" [evModified] (\f -> writeFile f "")
-        (\f -> when poll (threadDelay $ 10^(6 :: Int)) >> writeFile f "foo")
-    , mkTest "delete file" [evRemoved] (\f -> writeFile f "") (\f -> removeFile f)
-    , mkTest "directories are ignored" [] (const $ return ())
-        (\f -> createDirectory f >> removeDirectory f)
-    ]
-  return $ t nested recursive poll
-  where
-    mkTest title evs prepare action nested recursive poll =
-      testCase title $
-        withTempDirectory testDirPath "test." $ \watchedDir -> do
-        let baseDir = if nested then watchedDir </> "subdir" else watchedDir
-            f = baseDir </> fileName
-            expect =
-              expectEvents poll
-                (if recursive then watchTree else watchDir)
-                watchedDir
-        createDirectoryIfMissing True baseDir
-        (prepare f >>
-         expect (if not nested || recursive then map ($ f) evs else []) (action f))
-          `finally` (isFile f >>= \b -> when b (removeFile f))
-
-    fileName = "testfile"
-
-
--------------------------------------------------------------------------------
-isFile :: FilePath -> IO Bool
-isFile p = handleJust h return checkFile
-  where
-    h e = if isDoesNotExistError e
-          then Just False
-          else Nothing
-    checkFile = isRegularFile <$> getFileStatus p
