diff --git a/examples/Client.hs b/examples/Client.hs
index 9ebf899ab9813b17cdaaa218b85d577d20e47d52..329526bb6eb1dcd54dbb1ac07060fe918f6fcde1 100644
--- a/examples/Client.hs
+++ b/examples/Client.hs
@@ -6,10 +6,12 @@ import Quasar
 import Quasar.Prelude
 import Quasar.Timer
 import Quasar.Wayland.Client
-import Quasar.Wayland.Client.Buffer
 import Quasar.Wayland.Client.JuicyPixels
+import Quasar.Wayland.Client.Surface
 import Quasar.Wayland.Protocol
 import Quasar.Wayland.Protocol.Generated
+import Quasar.Wayland.Shm
+import Quasar.Wayland.Surface
 
 import Codec.Picture
 
@@ -39,8 +41,8 @@ mkDimensions width height = Dimensions { width, height, aspect }
 mkPosition :: Dimensions -> Int -> Int -> Position
 mkPosition dimensions pixelX pixelY = Position { dimensions, pixelX, pixelY, u, v, x, y }
   where
-    width' = width dimensions
-    height' = height dimensions
+    width' = dimensions.width
+    height' = dimensions.height
     u :: Double
     u = (fromIntegral pixelX) / (fromIntegral width')
     v :: Double
@@ -52,9 +54,10 @@ mkPosition dimensions pixelX pixelY = Position { dimensions, pixelX, pixelY, u,
     y :: Double
     y = (fromIntegral $ pixelY - (div height' 2)) / (fromIntegral innerRadius)
 
-color :: RealFrac a => a -> a -> a -> PixelRGBA8
+color :: forall a. RealFrac a => a -> a -> a -> PixelRGBA8
 color r g b = PixelRGBA8 (toWord r) (toWord g) (toWord b) 255
   where
+    toWord :: a -> Word8
     toWord = truncate . (* 255) . (max 0) . (min 1)
 
 gradient :: Position -> PixelRGBA8
@@ -78,22 +81,24 @@ mkImage fn = generateImage pixel width height
 
 main :: IO ()
 main = do
-  runQuasarAndExit (stderrLogger LogLevelWarning) do
+  _ <- runQuasarAndExit (stderrLogger LogLevelWarning) do
     traceIO "Connecting"
     client <- connectWaylandClient
     traceIO "Connected"
 
     join $ liftIO $ atomically do
 
-      wlCompositor <- bindSingleton @Interface_wl_compositor client.registry
+      -- wlSurface <- wlCompositor.create_surface
+      -- setMessageHandler wlSurface EventHandler_wl_surface {
+      --   enter = \_ -> pure (),
+      --   leave = \_ -> pure ()
+      -- }
 
-      shm <- newShmBufferManager client
-
-      wlSurface <- wlCompositor.create_surface
-      setMessageHandler wlSurface EventHandler_wl_surface {
-        enter = \_ -> pure (),
-        leave = \_ -> pure ()
-      }
+      -- wlSurface2 <- wlCompositor.create_surface
+      -- setMessageHandler wlSurface2 EventHandler_wl_surface {
+      --   enter = \_ -> pure (),
+      --   leave = \_ -> pure ()
+      -- }
 
       --xdgWmBase <- bindSingleton @Interface_xdg_wm_base client.registry
       --setMessageHandler xdgWmBase EventHandler_xdg_wm_base {
@@ -113,52 +118,80 @@ main = do
       --xdgToplevel.set_title "foobar"
 
 
+      (surface, wlSurface) <- newClientSurface @ShmBufferBackend client
+      (surface2, wlSurface2) <- newClientSurface @ShmBufferBackend client
+
+
       wlrLayerShell <- bindSingleton @Interface_zwlr_layer_shell_v1 client.registry
 
       configuredVar <- newTVar False
+      configuredVar2 <- newTVar False
 
       wlrLayerSurface <- wlrLayerShell.get_layer_surface wlSurface Nothing 2 "demo"
       setMessageHandler wlrLayerSurface EventHandler_zwlr_layer_surface_v1 {
-        configure = \serial width height -> do
+        configure = \serial _width _height -> do
             wlrLayerSurface.ack_configure serial
             writeTVar configuredVar True,
         closed = pure ()
       }
       wlrLayerSurface.set_size 512 512
+      wlrLayerSurface.set_anchor 1
+
+      wlrLayerSurface2 <- wlrLayerShell.get_layer_surface wlSurface2 Nothing 2 "demo"
+      setMessageHandler wlrLayerSurface2 EventHandler_zwlr_layer_surface_v1 {
+        configure = \serial _width _height -> do
+            wlrLayerSurface2.ack_configure serial
+            writeTVar configuredVar2 True,
+        closed = pure ()
+      }
+      wlrLayerSurface2.set_size 512 512
+      wlrLayerSurface2.set_anchor 2
 
       -- Commit role
       wlSurface.commit
+      wlSurface2.commit
       -- Should await first `configure` event
 
       pure do
-        buffer <- liftIO $ toImageBuffer shm (mkImage gradient)
+        buffer <- liftIO $ toImageBuffer (mkImage gradient)
+        buffer2 <- liftIO $ toImageBuffer (mkImage solidColor)
 
         liftIO $ atomically do
           check =<< readTVar configuredVar
-          wlSurface.attach (Just buffer) 0 0
-          wlSurface.commit
-
-        await =<< newDelay 1000000
-
-        buffer2 <- liftIO $ toImageBuffer shm (mkImage solidColor)
-
-        liftIO $ atomically do
-          wlSurface.attach (Just buffer2) 0 0
-          wlSurface.damage 0 0 42 42
-          wlSurface.commit
-
-        await =<< newDelay 1000000
+          check =<< readTVar configuredVar2
+          commitSurface surface SurfaceCommit {
+            buffer = Just buffer,
+            offset = (0, 0),
+            bufferDamage = DamageAll
+          }
+          commitSurface surface2 SurfaceCommit {
+            buffer = Just buffer2,
+            offset = (0, 0),
+            bufferDamage = DamageList [Rectangle 0 0 42 42]
+          }
+          --destroyBuffer buffer
+          --destroyBuffer buffer2
+
+        await =<< newDelay 100000
+        traceIO "Waiting 2s"
+        await =<< newDelay 2000000
 
         liftIO $ atomically do
-          wlSurface.attach (Nothing) 0 0
-          wlSurface.damage 100 100 42 42
-          wlSurface.commit
-
-        traceIO . ("shm buffer formats: " <>) . mconcat . intersperse ", " . fmap show . toList =<< await shm.formats
+          commitSurface surface SurfaceCommit {
+            buffer = Nothing,
+            offset = (0, 0),
+            bufferDamage = DamageList []
+          }
+          commitSurface surface2 SurfaceCommit {
+            buffer = Nothing,
+            offset = (0, 0),
+            bufferDamage = DamageList []
+          }
+
+        -- traceIO . ("shm buffer formats: " <>) . mconcat . intersperse ", " . fmap show . toList =<< await shm.formats
 
         pure ()
 
-    traceIO "Waiting 2s"
-    await =<< newDelay 2000000
+    await =<< newDelay 1000000
     traceIO "Closing"
   traceIO "Closed"
diff --git a/quasar-wayland.cabal b/quasar-wayland.cabal
index b7e075baae2ab2b3149b6bf323039fb059b740ff..0e301372e65f64f52d9688faa889123c87166412 100644
--- a/quasar-wayland.cabal
+++ b/quasar-wayland.cabal
@@ -75,7 +75,7 @@ library
   import: shared-properties
   exposed-modules:
     Quasar.Wayland.Client
-    Quasar.Wayland.Client.Buffer
+    Quasar.Wayland.Client.ShmBuffer
     Quasar.Wayland.Client.JuicyPixels
     Quasar.Wayland.Client.Registry
     Quasar.Wayland.Client.Socket
diff --git a/src/Quasar/Wayland/Client/Buffer.hs b/src/Quasar/Wayland/Client/Buffer.hs
deleted file mode 100644
index 796cc8c2acdfe6b8e213d0c598be181867a572ae..0000000000000000000000000000000000000000
--- a/src/Quasar/Wayland/Client/Buffer.hs
+++ /dev/null
@@ -1,103 +0,0 @@
-module Quasar.Wayland.Client.Buffer (
-  -- * wl_shm
-  ShmBufferManager(formats),
-  newShmBufferManager,
-  newShmPool,
-  newShmBuffer,
-) where
-
-import Control.Monad.Catch
-import Data.Set qualified as Set
-import Foreign
-import Quasar
-import Quasar.Prelude
-import Quasar.Wayland.Client
-import Quasar.Wayland.Protocol
-import Quasar.Wayland.Protocol.Generated
-import Quasar.Wayland.Utils.SharedMemory
-import System.Posix.IO (closeFd)
-import System.Posix.Types (Fd)
-
-
-data ShmBufferManager = ShmBufferManager {
-  wlShm :: Object 'Client Interface_wl_shm,
-  formats :: Future (Set.Set Word32)
-}
-
-newShmBufferManager :: WaylandClient -> STM ShmBufferManager
-newShmBufferManager client = do
-  formatsVar <- newTVar mempty
-
-  wlShm <- bindSingleton @Interface_wl_shm client.registry
-  setEventHandler wlShm $ EventHandler_wl_shm {
-    format = modifyTVar formatsVar . Set.insert
-  }
-
-  -- Formats are emittet all at once; sync ensures the list is complete
-  formatListComplete <- client.sync
-  let formats = formatListComplete >> unsafeAwaitSTM (readTVar formatsVar)
-
-  pure ShmBufferManager {
-    wlShm,
-    formats
-  }
-
-data ShmPool = ShmPool {
-  wlShmPool :: Object 'Client Interface_wl_shm_pool,
-  shmPtr :: TVar (Maybe (ForeignPtr Word8))
-}
-
-newShmPool :: ShmBufferManager -> Int32 -> IO ShmPool
-newShmPool shm size = do
-  (wlShmPool, ptr) <- trySendShm size (\fd -> shm.wlShm.create_pool fd size)
-
-  shmPtr <- newTVarIO (Just ptr)
-
-  pure ShmPool {
-    wlShmPool,
-    shmPtr
-  }
-
-
-newShmBuffer
-  :: ShmBufferManager
-  -> Int32
-  -> Int32
-  -> IO (Object 'Client Interface_wl_buffer, ForeignPtr Word32)
-newShmBuffer shm width height = do
-  (wlShmPool, ptr) <- trySendShm size (\fd -> shm.wlShm.create_pool fd size)
-
-  wlBuffer <- liftIO $ atomically do
-    wlBuffer <- wlShmPool.create_buffer offset width height stride pixelFormat
-    setEventHandler wlBuffer EventHandler_wl_buffer {
-      -- TODO
-      release = pure () -- wlBuffer.destroy
-    }
-    pure wlBuffer
-
-  atomically wlShmPool.destroy
-
-  pure (wlBuffer, castForeignPtr ptr)
-
-  where
-    bytePerPixel = 4
-    offset = 0
-    stride = width * bytePerPixel
-    size = width * height * bytePerPixel
-    pixelFormat = 0 -- argb8888
-
-
-trySendShm :: Int32 -> (Fd -> STM a) -> IO (a, ForeignPtr Word8)
-trySendShm size sendAction = do
-  fd <- memfdCreate $ fromIntegral size
-
-  -- Has to be created before sending since the fd will be closed when sent
-  ptr <- mmap fd $ fromIntegral size
-
-  -- Fd ownership is transferred to the outbox (it will be closed after it has
-  -- been sent)
-  result <- atomically (sendAction fd)
-    `onException`
-      (closeFd fd >> finalizeForeignPtr ptr)
-
-  pure (result, ptr)
diff --git a/src/Quasar/Wayland/Client/JuicyPixels.hs b/src/Quasar/Wayland/Client/JuicyPixels.hs
index 6ce293f466c17c34096e99e6fdd22b7e26c4284a..b331f6db44b0aa92448e8cbc4c1cfe46dce8160a 100644
--- a/src/Quasar/Wayland/Client/JuicyPixels.hs
+++ b/src/Quasar/Wayland/Client/JuicyPixels.hs
@@ -7,18 +7,18 @@ module Quasar.Wayland.Client.JuicyPixels (
 import Codec.Picture
 import Foreign
 import Quasar.Prelude
-import Quasar.Wayland.Client.Buffer
-import Quasar.Wayland.Protocol
-import Quasar.Wayland.Protocol.Generated
+import Quasar.Wayland.Client.ShmBuffer
+import Quasar.Wayland.Shm
+import Quasar.Wayland.Surface
 
-loadImageBuffer :: ShmBufferManager -> FilePath -> IO (Object 'Client Interface_wl_buffer)
-loadImageBuffer shm path = do
+loadImageBuffer :: FilePath -> IO (Buffer ShmBufferBackend)
+loadImageBuffer path = do
   image <- either fail (pure . convertRGBA8) =<< readImage path
-  toImageBuffer shm image
+  toImageBuffer image
 
-toImageBuffer :: ShmBufferManager -> Image PixelRGBA8 -> IO (Object 'Client Interface_wl_buffer)
-toImageBuffer shm image = do
-  (buffer, ptr) <- newShmBuffer shm (fromIntegral (imageWidth image)) (fromIntegral (imageHeight image))
+toImageBuffer :: Image PixelRGBA8 -> IO (Buffer ShmBufferBackend)
+toImageBuffer image = do
+  (buffer, ptr) <- newLocalShmBuffer (fromIntegral (imageWidth image)) (fromIntegral (imageHeight image))
   let
     width = imageWidth image
     height = imageHeight image
diff --git a/src/Quasar/Wayland/Client/ShmBuffer.hs b/src/Quasar/Wayland/Client/ShmBuffer.hs
new file mode 100644
index 0000000000000000000000000000000000000000..3f5a5d5e0f05b6e66fe48297b24c2f132459e04f
--- /dev/null
+++ b/src/Quasar/Wayland/Client/ShmBuffer.hs
@@ -0,0 +1,51 @@
+module Quasar.Wayland.Client.ShmBuffer (
+  -- * wl_shm
+  newLocalShmPool,
+  newLocalShmBuffer,
+) where
+
+import Control.Monad.Catch
+import Foreign
+import Quasar.Prelude
+import Quasar.Wayland.Shm
+import Quasar.Wayland.Surface
+import Quasar.Wayland.Utils.SharedMemory
+import System.Posix.IO (closeFd)
+
+
+newLocalShmPool :: Int32 -> IO (ShmPool, ForeignPtr Word8)
+newLocalShmPool size = do
+  fd <- memfdCreate $ fromIntegral size
+
+  ptr <- mmap fd $ fromIntegral size
+
+  -- Passes ownership of the fd to the pool
+  pool <- atomically (newShmPool fd size)
+    `onException`
+      (closeFd fd >> finalizeForeignPtr ptr)
+
+  pure (pool, ptr)
+
+
+newLocalShmBuffer
+  :: Int32
+  -> Int32
+  -> IO (Buffer ShmBufferBackend, ForeignPtr Word32)
+newLocalShmBuffer width height = do
+  (pool, ptr) <- newLocalShmPool size
+
+  atomically do
+    buffer <- newShmBuffer pool offset width height stride pixelFormat
+
+    -- Pool won't be reused
+    destroyShmPool pool
+
+    pure (buffer, castForeignPtr ptr)
+
+  where
+    bytePerPixel = 4
+    offset = 0
+    stride = width * bytePerPixel
+    size = width * height * bytePerPixel
+    pixelFormat = 0 -- argb8888
+