ymonad-0.1.0.0: src/YMonad/Backend/River/Decode.hs
{-# LANGUAGE OverloadedStrings #-}
module YMonad.Backend.River.Decode (
decodeCallback,
ingestBytes,
) where
import Data.ByteString qualified as BS
import Data.Map.Strict qualified as M
import Data.Text qualified as T
import YMonad.Backend.River.Types
import YMonad.Core
import YMonad.Protocol.Wayland.Types
import YMonad.Protocol.Wayland.Wire
decodeCallback :: RiverCallback -> Either BackendFault [YEvent]
decodeCallback cb = Right $ case cb of
RiverViewCreated vid meta -> [EvViewMapped vid meta]
RiverViewDestroyed vid -> [EvViewUnmapped vid]
RiverOutputDiscovered oid info -> [EvOutputAdded oid info]
RiverOutputLost oid -> [EvOutputRemoved oid]
RiverSeatFocus mvid -> [EvFocusChanged mvid]
RiverManageStart -> []
RiverRenderStart -> []
RiverUserCommand cmd -> [EvUserCommand cmd]
RiverActionTriggered _ -> []
RiverProtocolFault msg -> [EvBackendFault (BackendFault msg)]
RiverTrace _msg -> []
ingestBytes :: RiverRuntime -> BS.ByteString -> Either BackendFault (RiverRuntime, [RiverCallback])
ingestBytes rt chunk = do
let buffered = rrInboundBuffer rt <> chunk
(frames, leftover) <- firstFault (decodeRawFrames buffered)
processFrames (rt {rrInboundBuffer = leftover}) id frames
where
processFrames runtime callbacks [] = Right (runtime, callbacks [])
processFrames runtime callbacks (frame : rest) = do
(runtime', produced) <- processFrame runtime frame
processFrames runtime' (callbacks . (produced ++)) rest
processFrame :: RiverRuntime -> RawFrame -> Either BackendFault (RiverRuntime, [RiverCallback])
processFrame rt frame = do
ifaceName <- case M.lookup (rawObjectId frame) (rrObjectInterfaces rt) of
Nothing -> Left (BackendFault ("Unknown Wayland object id: " <> tshow (rawObjectId frame)))
Just name -> Right name
spec <- case lookupEventByOpcode (rrProtocol rt) ifaceName (rawOpcode frame) of
Nothing -> Left (BackendFault ("Unknown event opcode " <> tshow (rawOpcode frame) <> " for interface " <> ifaceName))
Just found -> Right found
args <- firstFault (decodeMessageArgs spec (rawPayload frame))
interpretEvent rt frame ifaceName spec args
interpretEvent :: RiverRuntime -> RawFrame -> Text -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
interpretEvent rt frame ifaceName spec args
| ifaceName == "wl_registry" && messageSpecName spec == "global" = handleRegistryGlobal rt args
| ifaceName == "wl_display" && messageSpecName spec == "error" =
Right (rt, [RiverProtocolFault ("wl_display.error: " <> T.intercalate ", " (map renderArg args))])
| ifaceName == "wl_callback" = handleCallbackEvent rt (rawObjectId frame) spec args
| ifaceName == "river_window_manager_v1" = handleManagerEvent rt spec args
| ifaceName == "river_xkb_binding_v1" = handleXkbBindingEvent rt (rawObjectId frame) spec args
| ifaceName == "river_window_v1" = handleWindowEvent rt (rawObjectId frame) spec args
| ifaceName == "river_output_v1" = handleOutputEvent rt (rawObjectId frame) spec args
| ifaceName == "river_seat_v1" = handleSeatEvent rt spec args
| otherwise = Right (rt, [])
handleRegistryGlobal :: RiverRuntime -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleRegistryGlobal rt [ArgUintValue globalName, ArgStringValue (Just ifaceName), ArgUintValue version]
| ifaceName == "river_window_manager_v1" =
case rrManagerObject rt of
Just _ -> Right (rt, [])
Nothing ->
let (managerId, rt1) = allocateObjectId rt
bindMessage =
WireMessage
{ wireObjectId = rrRegistryObject rt1
, wireInterface = "wl_registry"
, wireMessageName = "bind"
, wireOpcode = 0
, wireArgs =
[ ArgUintValue globalName
, ArgStringValue (Just ifaceName)
, ArgUintValue version
, ArgNewIdValue managerId
]
}
rt2 =
rt1
{ rrManagerObject = Just managerId
, rrObjectInterfaces = M.insert managerId ifaceName (rrObjectInterfaces rt1)
, rrOutgoing = rrOutgoing rt1 ++ [bindMessage]
}
in Right (rt2, [RiverTrace ("bound river_window_manager_v1 object " <> tshow managerId)])
| ifaceName == "river_xkb_bindings_v1" =
case rrXkbBindingsObject rt of
Just _ -> Right (rt, [])
Nothing ->
let (bindingsId, rt1) = allocateObjectId rt
bindMessage =
WireMessage
{ wireObjectId = rrRegistryObject rt1
, wireInterface = "wl_registry"
, wireMessageName = "bind"
, wireOpcode = 0
, wireArgs =
[ ArgUintValue globalName
, ArgStringValue (Just ifaceName)
, ArgUintValue version
, ArgNewIdValue bindingsId
]
}
rt2 =
rt1
{ rrXkbBindingsObject = Just bindingsId
, rrObjectInterfaces = M.insert bindingsId ifaceName (rrObjectInterfaces rt1)
, rrOutgoing = rrOutgoing rt1 ++ [bindMessage]
}
in Right (rt2, [RiverTrace ("bound river_xkb_bindings_v1 object " <> tshow bindingsId)])
| otherwise = Right (rt, [])
handleRegistryGlobal _ _ = Left (BackendFault "Malformed wl_registry.global payload")
handleCallbackEvent :: RiverRuntime -> Word32 -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleCallbackEvent rt objectId spec args = case messageSpecName spec of
"done" -> case args of
[ArgUintValue _callbackData]
| Just objectId == rrStartupSyncObject rt ->
let rt' =
rt
{ rrStartupSyncObject = Nothing
, rrRegistryReady = True
, rrObjectInterfaces = M.delete objectId (rrObjectInterfaces rt)
}
in Right (rt', [RiverTrace "registry roundtrip complete"])
[ArgUintValue _callbackData] -> Right (rt, [])
unexpectedDoneArgs -> Left (BackendFault ("Malformed wl_callback.done payload: " <> tshow unexpectedDoneArgs))
_otherCallbackEvent -> Right (rt, [])
handleManagerEvent :: RiverRuntime -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleManagerEvent rt spec args = case messageSpecName spec of
"manage_start" -> Right (rt {rrPhase = RiverPhaseManage, rrManageDirtySent = False}, [RiverTrace "manage_start", RiverManageStart])
"render_start" -> Right (rt {rrPhase = RiverPhaseRender}, [RiverTrace "render_start", RiverRenderStart])
"window" -> case args of
[ArgNewIdValue windowId] ->
let vid = ViewId (fromIntegral windowId)
(nodeId, rt1) = allocateObjectId rt
getNode =
WireMessage
{ wireObjectId = windowId
, wireInterface = "river_window_v1"
, wireMessageName = "get_node"
, wireOpcode = opcodeOf rt1 "river_window_v1" "get_node"
, wireArgs = [ArgNewIdValue nodeId]
}
handle = RiverViewHandle windowId (Just nodeId) "" "" Nothing
rt2 =
rt1
{ rrViews = M.insert vid handle (rrViews rt1)
, rrViewsByObjectId = M.insert windowId vid (rrViewsByObjectId rt1)
, rrObjectInterfaces = M.insert nodeId "river_node_v1" (M.insert windowId "river_window_v1" (rrObjectInterfaces rt1))
, rrOutgoing = rrOutgoing rt1 ++ [getNode]
}
meta = ViewMeta "" "" False
in Right (rt2, [RiverTrace ("manager announced window " <> tshow (unViewId vid) <> " (object " <> tshow windowId <> ")"), RiverViewCreated vid meta])
unexpectedWindowArgs -> Left (BackendFault ("Malformed river_window_manager_v1.window payload: " <> tshow unexpectedWindowArgs))
"output" -> case args of
[ArgNewIdValue outputObj] ->
let oid = OutputId (fromIntegral outputObj)
handle = RiverOutputHandle outputObj Nothing Nothing
rt' =
rt
{ rrOutputs = M.insert oid handle (rrOutputs rt)
, rrOutputsByObjectId = M.insert outputObj oid (rrOutputsByObjectId rt)
, rrObjectInterfaces = M.insert outputObj "river_output_v1" (rrObjectInterfaces rt)
}
in Right (rt', [])
unexpectedOutputArgs -> Left (BackendFault ("Malformed river_window_manager_v1.output payload: " <> tshow unexpectedOutputArgs))
"seat" -> case args of
[ArgNewIdValue seatObj] ->
let seat = RiverSeatHandle seatObj
rt' =
rt
{ rrSeat = Just seat
, rrObjectInterfaces = M.insert seatObj "river_seat_v1" (rrObjectInterfaces rt)
}
in Right (rt', [RiverTrace ("seat discovered: object " <> tshow seatObj)])
unexpectedSeatArgs -> Left (BackendFault ("Malformed river_window_manager_v1.seat payload: " <> tshow unexpectedSeatArgs))
"unavailable" -> Right (rt, [RiverProtocolFault "river window management unavailable"])
_otherManagerEvent -> Right (rt, [])
handleXkbBindingEvent :: RiverRuntime -> Word32 -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleXkbBindingEvent rt objectId spec _args =
case bindingActionForObject rt objectId of
Nothing -> Right (rt, [])
Just action -> case messageSpecName spec of
"pressed" -> Right (rt, [RiverActionTriggered action])
"released" -> Right (rt, [])
"stop_repeat" -> Right (rt, [])
_otherBindingEvent -> Right (rt, [])
handleWindowEvent :: RiverRuntime -> Word32 -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleWindowEvent rt objectId spec args = case M.lookup objectId (rrViewsByObjectId rt) of
Nothing -> Right (rt, [])
Just vid -> case messageSpecName spec of
"closed" ->
let rt' =
rt
{ rrViews = M.delete vid (rrViews rt)
, rrViewsByObjectId = M.delete objectId (rrViewsByObjectId rt)
}
in Right (rt', [RiverViewDestroyed vid])
"title" -> case args of
[ArgStringValue maybeTitle] ->
let title = fromMaybe "" maybeTitle
in Right (updateViewMeta rt vid (\h -> h {riverViewTitle = title}), [])
unexpectedTitleArgs -> Left (BackendFault ("Malformed river_window_v1.title payload: " <> tshow unexpectedTitleArgs))
"app_id" -> case args of
[ArgStringValue maybeAppId] ->
let appId = fromMaybe "" maybeAppId
in Right (updateViewMeta rt vid (\h -> h {riverViewAppId = appId}), [])
unexpectedAppIdArgs -> Left (BackendFault ("Malformed river_window_v1.app_id payload: " <> tshow unexpectedAppIdArgs))
"dimensions" -> case args of
[ArgIntValue w, ArgIntValue h] ->
let size = Size (fromIntegral w) (fromIntegral h)
rt' = updateViewMeta rt vid (\hnd -> hnd {riverViewDimensions = Just size})
in Right (rt', [RiverTrace ("window " <> tshow (unViewId vid) <> " dimensions " <> tshow w <> "x" <> tshow h)])
unexpectedDimensionsArgs -> Left (BackendFault ("Malformed river_window_v1.dimensions payload: " <> tshow unexpectedDimensionsArgs))
_otherWindowEvent -> Right (rt, [])
handleOutputEvent :: RiverRuntime -> Word32 -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleOutputEvent rt objectId spec args = case M.lookup objectId (rrOutputsByObjectId rt) of
Nothing -> Right (rt, [])
Just oid -> case messageSpecName spec of
"removed" ->
let rt' =
rt
{ rrOutputs = M.delete oid (rrOutputs rt)
, rrOutputsByObjectId = M.delete objectId (rrOutputsByObjectId rt)
}
in Right (rt', [RiverOutputLost oid])
"position" -> case args of
[ArgIntValue x, ArgIntValue y] ->
emitOutput rt oid (\h -> h {riverOutputPosition = Just (Point (fromIntegral x) (fromIntegral y))})
unexpectedPositionArgs -> Left (BackendFault ("Malformed river_output_v1.position payload: " <> tshow unexpectedPositionArgs))
"dimensions" -> case args of
[ArgIntValue w, ArgIntValue h] ->
emitOutput rt oid (\handle -> handle {riverOutputSize = Just (Size (fromIntegral w) (fromIntegral h))})
unexpectedDimensionsArgs -> Left (BackendFault ("Malformed river_output_v1.dimensions payload: " <> tshow unexpectedDimensionsArgs))
_otherOutputEvent -> Right (rt, [])
handleSeatEvent :: RiverRuntime -> MessageSpec -> [ArgValue] -> Either BackendFault (RiverRuntime, [RiverCallback])
handleSeatEvent rt spec args = case messageSpecName spec of
"pointer_enter" -> Right (rt, [])
"window_interaction" -> case args of
[ArgObjectValue (Just windowObj)] ->
let mVid = M.lookup windowObj (rrViewsByObjectId rt)
in Right (rt, [RiverSeatFocus mVid])
_unexpectedWindowInteractionArgs -> Right (rt, [])
_otherSeatEvent -> Right (rt, [])
emitOutput :: RiverRuntime -> OutputId -> (RiverOutputHandle -> RiverOutputHandle) -> Either BackendFault (RiverRuntime, [RiverCallback])
emitOutput rt oid f = case M.lookup oid (rrOutputs rt) of
Nothing -> Right (rt, [])
Just handle ->
let updated = f handle
rt' = rt {rrOutputs = M.insert oid updated (rrOutputs rt)}
in case (riverOutputPosition updated, riverOutputSize updated) of
(Just point, Just size) ->
let info =
OutputInfo
{ outputName = "river-output-" <> tshow (unOutputId oid)
, outputRect = Rect (pointX point) (pointY point) (sizeWidth size) (sizeHeight size)
}
in Right (rt', [RiverOutputDiscovered oid info])
_incompleteOutputInfo -> Right (rt', [])
updateViewMeta :: RiverRuntime -> ViewId -> (RiverViewHandle -> RiverViewHandle) -> RiverRuntime
updateViewMeta rt vid f = case M.lookup vid (rrViews rt) of
Nothing -> rt
Just handle -> rt {rrViews = M.insert vid (f handle) (rrViews rt)}
opcodeOf :: RiverRuntime -> Text -> Text -> Word16
opcodeOf rt ifaceName msgName =
maybe
0
messageSpecOpcode
(lookupRequest (rrProtocol rt) ifaceName msgName)
firstFault :: Either String a -> Either BackendFault a
firstFault = either (Left . BackendFault . T.pack) Right
renderArg :: ArgValue -> Text
renderArg arg = case arg of
ArgIntValue n -> tshow n
ArgUintValue n -> tshow n
ArgStringValue Nothing -> "<null>"
ArgStringValue (Just txt) -> txt
ArgObjectValue Nothing -> "object:null"
ArgObjectValue (Just oid) -> "object:" <> tshow oid
ArgNewIdValue oid -> "new_id:" <> tshow oid
tshow :: (Show a) => a -> Text
tshow = T.pack . show