diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
+# 1.2.5.4 November 2024
+
+* Bump `cabal-version` to 1.18.
+* README: alert of malfunction in multi-threaded settings.
+* Tested with GHC 8.0 - 9.12.0.
+
+# 1.2.5.3 August 2022
+
+* Tested with GHC 7.0 - 9.4.1.
+* Remove remnants of GHC 6.x support.
+* Silence incomplete pattern matching warning, refactor code.
+* Add section about limitations to README.
+
+# 1.2.5.2 November 2021
+
+* Tested with GHC 7.0 - 9.2.
+* Silence warning caused by missing `other-modules` in cabal file.
+* Add README and CHANGELOG to dist.
+
+# 1.2.5.1 July 2019
+
+No changelog for this and earlier versions.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+[![Hackage version](https://img.shields.io/hackage/v/silently.svg?label=Hackage&color=informational)](http://hackage.haskell.org/package/silently)
+[![silently on Stackage Nightly](https://stackage.org/package/silently/badge/nightly)](https://stackage.org/nightly/package/silently)
+[![Stackage LTS version](https://www.stackage.org/package/silently/badge/lts?label=Stackage)](https://www.stackage.org/package/silently)
+[![Cabal build](https://github.com/hspec/silently/workflows/Haskell-CI/badge.svg)](https://github.com/hspec/silently/actions)
+
+# silently
+
+Silently is a package that allows you to run an `IO` action and
+prevent it from writing to `stdout`, or any other handle, by using
+`silence`. Or you can capture the output for yourself using `capture`.
+
+For example, the program
+```haskell
+ import System.IO.Silently
+
+ main = do
+   putStr "putStrLn: " >> putStrLn "puppies!"
+   putStr "silenced: " >> silence (putStrLn "kittens!")
+   putStrLn ""
+   (captured, result) <- capture (putStr "wookies!" >> return 123)
+   putStr "captured: " >> putStrLn captured
+   putStr "returned: " >> putStrLn (show result)
+```
+will print:
+```
+ putStrLn: puppies!
+ silenced:
+ captured: wookies!
+ returned: 123
+```
+
+## Not thread-safe
+
+Since all threads of a process share the standard output handles `stdout` and `stderr`,
+capturing output to these handle will capture the output of __all threads__,
+not just the one running the action under `capture`.
+
+In essence, this library does not work in a situation where multiple threads are writing to the handle
+whose output _produced by the given action_ we want to capture.
+
+See:
+-  https://github.com/hspec/silently/issues/6
+
+## Further limitations
+
+Capturing/silencing might not work as expected if the action uses the FFI
+or conceals output under `unsafePerformIO` or similar unsafe operations.
+
+Examples:
+- FFI: https://github.com/hspec/silently/issues/3
+- `unsafePerformIO`: https://github.com/bos/filemanip/issues/22
diff --git a/silently.cabal b/silently.cabal
--- a/silently.cabal
+++ b/silently.cabal
@@ -1,40 +1,66 @@
+cabal-version: 1.18
 name: silently
-version: 1.2.4.1
-cabal-version: >= 1.8
+version: 1.2.5.4
 build-type: Simple
 license: BSD3
 license-file: LICENSE
+author: Trystan Spangler
 copyright: (c) Trystan Spangler 2011
-maintainer: trystan.s@comcast.net
-stability:
-homepage: https://github.com/trystan/silently
-package-url: https://github.com/trystan/silently
-bug-reports: https://github.com/trystan/silently/issues
+maintainer: Simon Hengel <sol@typeful.net>, Andreas Abel
+homepage: https://github.com/hspec/silently
+package-url: https://github.com/hspec/silently
+bug-reports: https://github.com/hspec/silently/issues
 synopsis: Prevent or capture writing to stdout and other handles.
-description: Prevent or capture writing to stdout and other handles.
-category:
-author: Trystan Spangler
-tested-with: GHC ==7.0
+description: Prevent or capture writing to stdout, stderr, and other handles.
+category: System, Testing
 
+tested-with:
+  GHC == 9.12.0
+  GHC == 9.10.1
+  GHC == 9.8.3
+  GHC == 9.6.6
+  GHC == 9.4.8
+  GHC == 9.2.8
+  GHC == 9.0.2
+  GHC == 8.10.7
+  GHC == 8.8.4
+  GHC == 8.6.5
+  GHC == 8.4.4
+  GHC == 8.2.2
+  GHC == 8.0.2
+
+extra-doc-files:
+  CHANGELOG.md
+  README.md
+
 source-repository head
   type: git
-  location: https://github.com/trystan/silently
+  location: https://github.com/hspec/silently
 
 Library
+  hs-source-dirs:
+      src
+  default-language:
+      Haskell98
+  exposed-modules:
+      System.IO.Silently
+
   build-depends:
-      base >=4 && <=5
+      base >= 4.3 && < 5
     , directory
     , deepseq
-  exposed-modules: System.IO.Silently
 
-  hs-source-dirs:
-      src
-
   if os(windows)
     cpp-options: -DWINDOWS
   if os(linux) || os(osx) || os(freebsd) || os(openbsd) || os(netbsd)
     cpp-options: -DUNIX
 
+  ghc-options:
+    -Wall
+  if impl(ghc >= 8)
+    ghc-options:
+      -Wcompat
+
 -- This tests the platform specific implementation.
 --
 -- NOTE: Cabal 1.10 can not deal with conditional (== if-else) options.  This
@@ -43,36 +69,42 @@
 -- As a consequence we can not use Hspec for testing, as this would result in
 -- depending on two different versions of silently at the same time!
 test-suite spec-specific
-  main-is:
-      Spec.hs
   type:
       exitcode-stdio-1.0
-  ghc-options:
-      -Wall -Werror -threaded
   hs-source-dirs:
       test
+  main-is:
+      Spec.hs
+  default-language:
+      Haskell98
+  ghc-options:
+      -Wall -threaded
   build-depends:
-      base
+      base  >= 4.3 && < 5
     , silently
     , directory
     , nanospec
+    , temporary
 
 -- This tests the generic implementation, that should work on all platforms.
 test-suite spec-generic
-  main-is:
-      Spec.hs
   type:
       exitcode-stdio-1.0
-  ghc-options:
-      -Wall -threaded
-      -- FIXME: use -Werror
-      -- -Wall -Werror -threaded
   hs-source-dirs:
       src
     , test
+  main-is:
+      Spec.hs
+  default-language:
+      Haskell98
+  ghc-options:
+      -Wall -threaded
+  other-modules:
+      System.IO.Silently
 
   build-depends:
-      base
+      base  >= 4.3 && < 5
     , deepseq
     , directory
     , nanospec
+    , temporary
diff --git a/src/System/IO/Silently.hs b/src/System/IO/Silently.hs
--- a/src/System/IO/Silently.hs
+++ b/src/System/IO/Silently.hs
@@ -1,32 +1,35 @@
 {-# LANGUAGE CPP #-}
--- | Need to prevent output to the terminal, a file, or stderr? Need to capture it and use it for
--- your own means? Now you can, with 'silence' and 'capture'.
+{-# LANGUAGE ScopedTypeVariables #-}
 
-module System.IO.Silently (
-  silence,
-  hSilence,
-  capture,
-  capture_,
-  hCapture,
-  hCapture_,
-) where
+-- | Need to prevent output to the terminal, a file, or stderr?
+--   Need to capture it and use it for your own means?
+--   Now you can, with 'silence' and 'capture'.
 
-import Prelude
+module System.IO.Silently
+  ( silence, hSilence
+  , capture, capture_, hCapture, hCapture_
+  ) where
 
-#if __GLASGOW_HASKELL__ >= 612
-import GHC.IO.Handle (hDuplicate, hDuplicateTo)
-#else
-import GHC.Handle (hDuplicate, hDuplicateTo)
-#endif
+import Prelude
 
-import System.IO
 import qualified Control.Exception as E
 import Control.DeepSeq
-import System.Directory (removeFile,getTemporaryDirectory)
+  ( deepseq )
 
+import GHC.IO.Handle
+  ( hDuplicate, hDuplicateTo )
+
+import System.Directory
+  ( getTemporaryDirectory, removeFile )
+import System.IO
+  ( Handle, IOMode(AppendMode), SeekMode(AbsoluteSeek)
+  , hClose, hFlush, hGetBuffering, hGetContents, hSeek, hSetBuffering
+  , openFile, openTempFile, stdout
+  )
+
 mNullDevice :: Maybe FilePath
 #ifdef WINDOWS
-mNullDevice = Just "NUL"
+mNullDevice = Just "\\\\.\\NUL"
 #elif UNIX
 mNullDevice = Just "/dev/null"
 #else
@@ -38,38 +41,46 @@
 silence = hSilence [stdout]
 
 -- | Run an IO action while preventing all output to the given handles.
-hSilence :: [Handle] -> IO a -> IO a
-hSilence handles action = case mNullDevice of
-  Just nullDevice -> E.bracket (openFile nullDevice AppendMode)
-                             hClose
-                             prepareAndRun
+hSilence :: forall a. [Handle] -> IO a -> IO a
+hSilence handles action =
+  case mNullDevice of
+    Just nullDevice ->
+      E.bracket (openFile nullDevice AppendMode)
+                hClose
+                prepareAndRun
+    Nothing -> withTempFile "silence" prepareAndRun
+  where
+    prepareAndRun :: Handle -> IO a
+    prepareAndRun tmpHandle = go handles
+      where
+        go []     = action
+        go (h:hs) = goBracket go tmpHandle h hs
 
-  Nothing -> do
-    tmpDir <- getTempOrCurrentDirectory
-    E.bracket (openTempFile tmpDir "silence")
-                               cleanup
-                               (prepareAndRun . snd)
 
- where
-  cleanup (tmpFile,tmpHandle) = do
-    hClose tmpHandle
-    removeFile tmpFile
-  prepareAndRun tmpHandle = go handles
-    where
-      go [] = action
-      go hs = goBracket go tmpHandle hs
-
+-- Provide a tempfile for the given action and remove it afterwards.
+withTempFile :: String -> (Handle -> IO a) -> IO a
+withTempFile tmpName action = do
+  tmpDir <- getTempOrCurrentDirectory
+  E.bracket (openTempFile tmpDir tmpName)
+            cleanup
+            (action . snd)
+  where
+    cleanup :: (FilePath, Handle) -> IO ()
+    cleanup (tmpFile, tmpHandle) = do
+      hClose tmpHandle
+      removeFile tmpFile
 
 getTempOrCurrentDirectory :: IO String
 getTempOrCurrentDirectory = getTemporaryDirectory `catchIOError` (\_ -> return ".")
   where
     -- NOTE: We can not use `catchIOError` from "System.IO.Error", it is only
-    -- availabel in base >= 4.4.
+    -- available in base >= 4.4.
     catchIOError :: IO a -> (IOError -> IO a) -> IO a
     catchIOError = E.catch
 
 -- | Run an IO action while preventing and capturing all output to stdout.
--- This will, as a side effect, create and delete a temp file in the temp directory or current directory if there is no temp directory.
+-- This will, as a side effect, create and delete a temp file in the temp directory
+-- or current directory if there is no temp directory.
 capture :: IO a -> IO (String, a)
 capture = hCapture [stdout]
 
@@ -82,30 +93,31 @@
 hCapture_ handles = fmap fst . hCapture handles
 
 -- | Run an IO action while preventing and capturing all output to the given handles.
--- This will, as a side effect, create and delete a temp file in the temp directory or current directory if there is no temp directory.
-hCapture :: [Handle] -> IO a -> IO (String, a)
-hCapture handles action = do
-  tmpDir <- getTempOrCurrentDirectory
-  E.bracket (openTempFile tmpDir "capture")
-                             cleanup
-                             (prepareAndRun . snd)
- where
-  cleanup (tmpFile,tmpHandle) = do
-    hClose tmpHandle
-    removeFile tmpFile
-  prepareAndRun tmpHandle = go handles
-    where
-      go [] = do
-              a <- action
-              mapM_ hFlush handles
-              hSeek tmpHandle AbsoluteSeek 0
-              str <- hGetContents tmpHandle
-              str `deepseq` return (str,a)
-      go hs = goBracket go tmpHandle hs
+-- This will, as a side effect, create and delete a temp file in the temp directory
+-- or current directory if there is no temp directory.
+hCapture :: forall a. [Handle] -> IO a -> IO (String, a)
+hCapture handles action = withTempFile "capture" prepareAndRun
+  where
+    prepareAndRun :: Handle -> IO (String, a)
+    prepareAndRun tmpHandle = go handles
+      where
+        go [] = do
+          a <- action
+          mapM_ hFlush handles
+          hSeek tmpHandle AbsoluteSeek 0
+          str <- hGetContents tmpHandle
+          str `deepseq` return (str, a)
+        go (h:hs) = goBracket go tmpHandle h hs
 
-goBracket :: ([Handle] -> IO a) -> Handle -> [Handle] -> IO a
-goBracket go tmpHandle (h:hs) = E.bracket (do old <- hDuplicate h
-                                              hDuplicateTo tmpHandle h
-                                              return old)
-                                        (\old -> hDuplicateTo old h >> hClose old)
-                                        (\_   -> go hs)
+goBracket :: ([Handle] -> IO a) -> Handle -> Handle -> [Handle] -> IO a
+goBracket go tmpHandle h hs = do
+  buffering <- hGetBuffering h
+  let redirect = do
+        old <- hDuplicate h
+        hDuplicateTo tmpHandle h
+        return old
+      restore old = do
+        hDuplicateTo old h
+        hSetBuffering h buffering
+        hClose old
+  E.bracket redirect restore (\_ -> go hs)
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -4,8 +4,10 @@
 import           System.IO
 import           System.IO.Silently
 import           System.Directory
+import           System.IO.Temp
 
 import           Control.Exception
+import           Control.Monad
 
 main :: IO ()
 main = hspec spec
@@ -26,3 +28,11 @@
   describe "capture" $ do
     it "captures stdout" $ do
       capture (putStr "foo" >> return 23) `shouldReturn` ("foo", 23 :: Int)
+
+  describe "hCapture" $ do
+    forM_ [NoBuffering, LineBuffering, BlockBuffering Nothing] $ \buffering -> do
+      it ("preserves " ++ show buffering) $ do
+        withSystemTempFile "silently" $ \_file h -> do
+          hSetBuffering h buffering
+          _ <- hCapture [h] (return ())
+          hGetBuffering h `shouldReturn` buffering
