packages feed

ymonad-0.1.0.0: src/YMonad/Backend/River/Interpreter.hs

module YMonad.Backend.River.Interpreter (
  connectRiver,
  recvEventsIO,
  flushTransportIO,
) where

import Control.Exception (bracketOnError, try)
import Data.ByteString qualified as BS
import Network.Socket
import Network.Socket.ByteString qualified as NSB
import System.FilePath ((</>))

import YMonad.Backend.River.Decode
import YMonad.Backend.River.Encode
import YMonad.Backend.River.Translate
import YMonad.Backend.River.Types
import YMonad.Core

connectRiver :: Maybe FilePath -> IO (Either BackendFault Socket)
connectRiver explicitPath = do
  resolved <- resolveSocketPath explicitPath
  case resolved of
    Left err -> pure (Left err)
    Right path -> do
      result <- (try (bracketOnError (socket AF_UNIX Stream defaultProtocol) close (\sock -> connect sock (SockAddrUnix path) >> pure sock)) :: IO (Either SomeException Socket))
      pure $ case result of
        Left ex -> Left (BackendFault ("Failed to connect to Wayland socket " <> toText path <> ": " <> toText (displayException ex)))
        Right sock -> Right sock

recvEventsIO :: Socket -> RiverRuntime -> IO (Either BackendFault (RiverRuntime, [YEvent]))
recvEventsIO sock rt = do
  recvResult <- (try (NSB.recv sock 65535) :: IO (Either SomeException BS.ByteString))
  case recvResult of
    Left ex -> pure (Left (BackendFault ("Wayland recv failed: " <> toText (displayException ex))))
    Right chunk ->
      if BS.null chunk
        then pure (Left (BackendFault "Wayland socket closed by compositor"))
        else do
          pure $ do
            (rt', callbacks) <- ingestBytes rt chunk
            events <- fmap concat (mapM decodeCallback callbacks)
            pure (rt', events)

flushTransportIO :: Socket -> RiverRuntime -> IO (Either BackendFault RiverRuntime)
flushTransportIO sock rt0 = do
  let rt1 = pumpPending rt0
      (rt2, bytes) = flushOutgoingBytes rt1
  if BS.null bytes
    then pure (Right rt2)
    else do
      sendResult <- (try (NSB.sendAll sock bytes) :: IO (Either SomeException ()))
      pure $ case sendResult of
        Left ex -> Left (BackendFault ("Wayland send failed: " <> toText (displayException ex)))
        Right () -> Right rt2

resolveSocketPath :: Maybe FilePath -> IO (Either BackendFault FilePath)
resolveSocketPath (Just path) = pure (Right path)
resolveSocketPath Nothing = do
  display <- lookupEnv "WAYLAND_DISPLAY"
  runtimeDir <- lookupEnv "XDG_RUNTIME_DIR"
  pure $ case (display, runtimeDir) of
    (Just disp, Just dir) -> Right (dir </> disp)
    (Nothing, _) -> Left (BackendFault "WAYLAND_DISPLAY is not set")
    (_, Nothing) -> Left (BackendFault "XDG_RUNTIME_DIR is not set")