diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@
 “outsourced” and simplified.
 
 *  All C code is removed, replaced with libraries from Hackage.
-There is still some use of the FFI.
+There is still some use of the FFI and hsc.
 
 *  Unicode is supported in titles and filenames, and Unicode glyphs
 are utilized in the interface.
diff --git a/hmp3-ng.cabal b/hmp3-ng.cabal
--- a/hmp3-ng.cabal
+++ b/hmp3-ng.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: hmp3-ng
-version: 2.20.0
+version: 2.20.1
 synopsis: A TUI mp3 player written in Haskell
 description:
   An mp3 player with a curses frontend.  Playlists are populated by
@@ -52,6 +52,7 @@
     Keyboard
     Keymap
     Playlist
+    Regex
     State
     Style
     Text
@@ -79,7 +80,6 @@
     posix-paths,
     process,
     random,
-    regex-posix,
     transformers,
     unix >=2.8,
     utf8-string,
diff --git a/src/Regex.hsc b/src/Regex.hsc
new file mode 100644
--- /dev/null
+++ b/src/Regex.hsc
@@ -0,0 +1,64 @@
+module Regex where
+
+import Base
+
+import Data.ByteString qualified as P
+import Data.ByteString.Unsafe qualified as P
+import Data.Int -- needed for whatever regoff_t might be
+import Foreign.C.Types (CInt(..), CSize(..))
+import Foreign.C.String (CString)
+import Foreign.Concurrent (newForeignPtr)
+import Foreign.ForeignPtr hiding (newForeignPtr)
+import Foreign.Marshal.Alloc
+import Foreign.Ptr (Ptr, nullPtr)
+import Foreign.Storable (pokeByteOff)
+
+#include <regex.h>
+
+newtype Regex = Regex (ForeignPtr ())
+
+compOptions :: CInt
+compOptions = #{const REG_EXTENDED} + #{const REG_NOSUB} + #{const REG_ICASE}
+
+compile :: ByteString -> Maybe Regex
+compile pat = unsafePerformIO $
+    P.useAsCString pat $ \patC -> do
+        preg <- mallocBytes #{size regex_t}
+        rc <- regcomp preg patC compOptions
+        if rc == 0
+            then Just . Regex <$> newForeignPtr preg (regfree preg *> free preg)
+            else Nothing <$ free preg
+
+execPosix, execSE, exec :: Regex -> ByteString -> Bool
+
+execPosix (Regex fp) str = unsafePerformIO $
+    withForeignPtr fp $ \preg ->
+    P.useAsCString str $ \cstr ->
+        (== 0) <$> regexec preg cstr 0 nullPtr 0
+
+-- More efficient.
+execSE (Regex fp) str = unsafePerformIO $
+    withForeignPtr fp $ \preg ->
+    P.unsafeUseAsCStringLen str $ \(cstr, len) ->
+    allocaBytes #{size regmatch_t} $ \pmatch -> do
+        #{poke regmatch_t, rm_so} pmatch (0 :: #{type regoff_t})
+        #{poke regmatch_t, rm_eo} pmatch (fromIntegral len :: #{type regoff_t})
+        (== 0) <$> regexec preg cstr 0 pmatch #{const REG_STARTEND}
+
+exec =
+#ifdef REG_STARTEND
+    execSE
+#else
+    execPosix
+#endif
+
+
+foreign import ccall unsafe "regcomp"
+    regcomp :: Ptr () -> CString -> CInt -> IO CInt
+
+foreign import ccall unsafe "regexec"
+    regexec :: Ptr () -> CString -> CSize -> Ptr () -> CInt -> IO CInt
+
+foreign import ccall unsafe "regfree"
+    regfree :: Ptr () -> IO ()
+
diff --git a/src/Text.hs b/src/Text.hs
--- a/src/Text.hs
+++ b/src/Text.hs
@@ -13,13 +13,13 @@
 ) where
 
 import Base
+import Regex qualified
 
 import Data.ByteString.Char8 qualified as P
 import Data.ByteString.UTF8 qualified as UTF8
 import Foreign.C.Types (CWchar(..), CInt(..))
 import GHC.Foreign qualified as GHC
 import GHC.IO.Encoding (getFileSystemEncoding)
-import Text.Regex.Posix (match, makeRegexOptsM, compIgnoreCase, compExtended, compNoSub)
 
 
 -- SText type and functions.
@@ -55,9 +55,8 @@
 
 -- | Swappable API for searching
 matches :: SText -> Maybe (SText -> Bool)
-matches (SText s _) =
-    match' <$> makeRegexOptsM (compIgnoreCase + compExtended + compNoSub) 0 s
-  where match' re (SText bs _) = match re bs
+matches (SText s _) = match <$> Regex.compile s
+  where match re (SText bs _) = Regex.exec re bs
 
 -- | Possible number.
 readIntM :: SText -> Maybe Int
