packages feed

i3ipc 0.1.0.1 → 0.2.0.0

raw patch · 12 files changed

+1763/−110 lines, 12 filesdep +exceptionsdep ~networkPVP ok

version bump matches the API change (PVP)

Dependencies added: exceptions

Dependency ranges changed: network

API changes (from Hackage documentation)

+ I3IPC: connectsway :: (MonadThrow m, MonadIO m) => m Socket
+ I3IPC: getSwaySocketPath :: MonadIO m => m (Maybe ByteString)
+ I3IPC: instance GHC.Classes.Eq I3IPC.I3Exception
+ I3IPC: instance GHC.Exception.Type.Exception I3IPC.I3Exception
+ I3IPC: instance GHC.Show.Show I3IPC.I3Exception
+ I3IPC.Event: UnknownChange :: WorkspaceChange
+ I3IPC.Event: WinUnknown :: WindowChange
+ I3IPC.Reply: UnknownBarPart :: BarPart
+ I3IPC.Reply: UnknownBorder :: NodeBorder
+ I3IPC.Reply: UnknownLayout :: NodeLayout
+ I3IPC.Reply: UnknownOrient :: NodeOrientation
+ I3IPC.Reply: UnknownProperty :: WindowProperty
+ I3IPC.Reply: UnknownType :: NodeType
- I3IPC: connecti3 :: IO Socket
+ I3IPC: connecti3 :: (MonadThrow m, MonadIO m) => m Socket
- I3IPC: getBarConfig :: Socket -> ByteString -> IO (Either String MsgReply)
+ I3IPC: getBarConfig :: MonadIO m => Socket -> ByteString -> m (Either String MsgReply)
- I3IPC: getBarConfig' :: Socket -> ByteString -> IO (Either String MsgReply)
+ I3IPC: getBarConfig' :: MonadIO m => Socket -> ByteString -> m (Either String MsgReply)
- I3IPC: getBarIds :: Socket -> IO (Either String BarIds)
+ I3IPC: getBarIds :: MonadIO m => Socket -> m (Either String BarIds)
- I3IPC: getBindingModes :: Socket -> IO (Either String MsgReply)
+ I3IPC: getBindingModes :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getBindingModes' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getBindingModes' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getConfig :: Socket -> IO (Either String MsgReply)
+ I3IPC: getConfig :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getConfig' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getConfig' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getMarks :: Socket -> IO (Either String MsgReply)
+ I3IPC: getMarks :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getMarks' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getMarks' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getOutputs :: Socket -> IO (Either String MsgReply)
+ I3IPC: getOutputs :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getOutputs' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getOutputs' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getReply :: Socket -> IO (Either String (Int, ByteString))
+ I3IPC: getReply :: MonadIO m => Socket -> m (Either String (Int, ByteString))
- I3IPC: getSocketPath :: IO (Maybe ByteString)
+ I3IPC: getSocketPath :: MonadIO m => m (Maybe ByteString)
- I3IPC: getSync :: Socket -> IO (Either String MsgReply)
+ I3IPC: getSync :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getSync' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getSync' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getTick :: Socket -> IO (Either String MsgReply)
+ I3IPC: getTick :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getTick' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getTick' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getTree :: Socket -> IO (Either String MsgReply)
+ I3IPC: getTree :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getTree' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getTree' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getVersion :: Socket -> IO (Either String MsgReply)
+ I3IPC: getVersion :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getVersion' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getVersion' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getWorkspaces :: Socket -> IO (Either String MsgReply)
+ I3IPC: getWorkspaces :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: getWorkspaces' :: Socket -> IO (Either String MsgReply)
+ I3IPC: getWorkspaces' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: receive :: Socket -> IO (Either String Response)
+ I3IPC: receive :: MonadIO m => Socket -> m (Either String Response)
- I3IPC: receive' :: Socket -> IO (Either String Response)
+ I3IPC: receive' :: MonadIO m => Socket -> m (Either String Response)
- I3IPC: receiveEvent :: Socket -> IO (Either String Event)
+ I3IPC: receiveEvent :: MonadIO m => Socket -> m (Either String Event)
- I3IPC: receiveEvent' :: Socket -> IO (Either String Event)
+ I3IPC: receiveEvent' :: MonadIO m => Socket -> m (Either String Event)
- I3IPC: receiveMsg :: Socket -> IO (Either String MsgReply)
+ I3IPC: receiveMsg :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: receiveMsg' :: Socket -> IO (Either String MsgReply)
+ I3IPC: receiveMsg' :: MonadIO m => Socket -> m (Either String MsgReply)
- I3IPC: runCommand :: Socket -> ByteString -> IO (Either String MsgReply)
+ I3IPC: runCommand :: MonadIO m => Socket -> ByteString -> m (Either String MsgReply)
- I3IPC: runCommand' :: Socket -> ByteString -> IO (Either String MsgReply)
+ I3IPC: runCommand' :: MonadIO m => Socket -> ByteString -> m (Either String MsgReply)
- I3IPC: subscribe :: (Either String Event -> IO ()) -> [Subscribe] -> IO ()
+ I3IPC: subscribe :: (MonadThrow m, MonadIO m) => (Either String Event -> m ()) -> [Subscribe] -> m ()
- I3IPC: subscribeM :: MonadIO m => (Either String Event -> m ()) -> [Subscribe] -> m ()
+ I3IPC: subscribeM :: (MonadThrow m, MonadIO m) => (Either String Event -> m ()) -> [Subscribe] -> m ()
- I3IPC.Message: sendMsg :: Socket -> MessageType -> IO Int64
+ I3IPC.Message: sendMsg :: MonadIO m => Socket -> MessageType -> m Int64
- I3IPC.Message: sendMsgPayload :: Socket -> MessageType -> ByteString -> IO Int64
+ I3IPC.Message: sendMsgPayload :: MonadIO m => Socket -> MessageType -> ByteString -> m Int64

Files

README.md view
@@ -2,19 +2,20 @@  [![Build Status](https://travis-ci.com/leshow/i3ipc.svg?branch=master)](https://travis-ci.com/leshow/i3ipc) -Haskell type-safe bindings for working with i3 using it's unix socket IPC+Haskell type-safe bindings for working with i3 using it's unix socket IPC. Sway is supposed to be protocl compatible with i3, I've included a function to bind to sway's socket also.  ## Subscribing  Subscribe to events:  ```haskell-import qualified I3IPC.Subscribe   as Sub-import           I3IPC              ( subscribe )+import qualified I3IPC.Subscribe         as Sub+import           I3IPC                   ( subscribe )+import           Control.Monad.IO.Class  -- will print all events main :: IO ()-main = subscribe print [Sub.Workspace, Sub.Window]+main = liftIO $ subscribe print [Sub.Workspace, Sub.Window] ```  An example of explicitly matching on some events and printing their fields:@@ -22,12 +23,13 @@ ```haskell import qualified I3IPC.Subscribe               as Sub import           I3IPC.Event-import           I3IPC                          ( subscribe )+import           I3IPC                         ( subscribe )+import           Control.Monad.IO.Class  main :: IO ()-main = subscribe handle [Sub.Workspace, Sub.Window]+main = liftIO $ subscribe handle [Sub.Workspace, Sub.Window]  where-  handle :: Either String Event -> IO ()+  handle :: MonadIO m => Either String Event -> m ()   handle (Right evt) = case evt of     Workspace WorkspaceEvent { wrk_current } -> print wrk_current     Window WindowEvent { win_container } -> print win_container@@ -43,10 +45,11 @@ import           I3IPC              ( connecti3                                     , getWorkspaces                                     )+import           Control.Monad.IO.Class                                      main :: IO () main = do-    soc <- connecti3+    soc <- liftIO $ connecti3     print $ getWorkspaces soc ``` @@ -57,10 +60,11 @@ import           I3IPC              ( connecti3                                     , receiveMsg                                     )+import           Control.Monad.IO.Class                                      main :: IO () main = do-    soc <- connecti3+    soc <- liftIO $ connecti3     print $ Msg.sendMsg soc Msg.Workspaces >> receiveMsg soc ``` 
i3ipc.cabal view
@@ -1,5 +1,5 @@ name:                i3ipc-version:             0.1.0.1+version:             0.2.0.0 description:         Library for controlling i3 through it's IPC. i3 communicates using a JSON interface over a unix socket.                      For JSON parsing I'm using Aeson. I've written out all the records and types to allow anyone to                       easily interact with i3 from a Haskell application.@@ -12,7 +12,7 @@ copyright:           2019 Evan Cameron category:            Lib build-type:          Simple-extra-source-files:  README.md+extra-source-files:  README.md test/event/*.json test/reply/*.json cabal-version:       >=1.10  library@@ -29,8 +29,9 @@                      , binary               >= 0.8.6 && < 0.10                      , text                 >= 1.2.1.0 && < 1.3                      , vector               >= 0.11.0 && < 0.13-                     , network              >= 2.6.3.5 && < 3.1+                     , network              >= 2.6.3.5 && < 3.2                      , typed-process        >= 0.2.4 && < 0.3+                     , exceptions           >= 0.8.1 && < 0.10.5                         default-language:    Haskell2010   ghc-options:         -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints 
src/I3IPC.hs view
@@ -19,6 +19,7 @@     -- ** Convenience functions     -- $func           getSocketPath+    , getSwaySocketPath     , Response(..)     , subscribe     , subscribeM@@ -28,6 +29,7 @@     , receiveMsg'     , getReply     , connecti3+    , connectsway     , receiveEvent     , receiveEvent'     , runCommand@@ -62,15 +64,17 @@ import           I3IPC.Reply  import           Control.Monad.IO.Class+import           Control.Exception                   ( Exception )+import           Control.Monad.Catch                 ( MonadThrow+                                                     , throwM+                                                     ) import           System.Environment                  ( lookupEnv ) import           Data.Maybe                          ( isJust ) import           Data.Semigroup                      ( (<>) ) import           System.Process.Typed                ( proc                                                      , readProcess                                                      )-import           System.Exit                         ( ExitCode(..)-                                                     , exitFailure-                                                     )+import           System.Exit                         ( ExitCode(..) )  import           Network.Socket               hiding ( send                                                      , sendTo@@ -82,14 +86,22 @@ import           Data.Binary.Get import           Data.Bifunctor                      ( second ) import qualified Data.ByteString.Lazy.Char8         as BL+import           Data.Typeable                       ( Typeable )+import qualified Data.Text                          as T import           Data.Bits                           ( testBit                                                      , clearBit                                                      ) +-- | Exception type+data I3Exception = ConnectException T.Text | ProcessException+    deriving stock (Show, Eq, Typeable)++instance Exception I3Exception+ -- | Get a new unix socket path from i3-getSocketPath :: IO (Maybe BL.ByteString)+getSocketPath :: MonadIO m => m (Maybe BL.ByteString) getSocketPath = do-    res <- lookupEnv "I3SOCK"+    res <- liftIO $ lookupEnv "I3SOCK"     if isJust res         then pure $ fmap BL.pack res         else do@@ -98,61 +110,81 @@                 then pure Nothing                 else pure $ Just (BL.filter (/= '\n') out) +-- | Get a new unix socket path from sway+getSwaySocketPath :: MonadIO m => m (Maybe BL.ByteString)+getSwaySocketPath = fmap BL.pack <$> liftIO (lookupEnv "SWAYSOCK")  -- | Subscribe with a list of 'I3IPC.Subscribe.Subscribe' types, and subscribe will to respond with specific 'I3IPC.Event.Event'-subscribe :: (Either String Evt.Event -> IO ()) -> [Sub.Subscribe] -> IO ()+subscribe+    :: (MonadThrow m, MonadIO m)+    => (Either String Evt.Event -> m ())+    -> [Sub.Subscribe]+    -> m () subscribe handle subtypes = do     soc <- connecti3     Msg.sendMsgPayload soc Msg.Subscribe (encode subtypes)         >> receiveMsg soc         >> handleSoc soc-        >> close soc+        >> liftIO (close soc)   where     handleSoc soc = do         r <- receiveEvent soc         handle r         handleSoc soc --- | A version of 'subscribe' that allows the use of any monad transformer on top of MonadIO+-- | A version of 'subscribe' that allows the use of any monad transformer on top of MonadIO (kept around for backwards compatibility) subscribeM-    :: MonadIO m => (Either String Evt.Event -> m ()) -> [Sub.Subscribe] -> m ()+    :: (MonadThrow m, MonadIO m)+    => (Either String Evt.Event -> m ())+    -> [Sub.Subscribe]+    -> m () subscribeM handle subtypes = do-    soc <- liftIO connecti3-    liftIO-        $  Msg.sendMsgPayload soc Msg.Subscribe (encode subtypes)+    soc <- connecti3+    Msg.sendMsgPayload soc Msg.Subscribe (encode subtypes)         >> receiveMsg soc         >> pure ()     handleSoc soc >> liftIO (close soc)   where     handleSoc soc = do-        r <- liftIO $ receiveEvent soc+        r <- receiveEvent soc         handle r         handleSoc soc  -- | Connect to an i3 socket and return it-connecti3 :: IO Socket+connecti3 :: (MonadThrow m, MonadIO m) => m Socket connecti3 = do-    soc <- socket AF_UNIX Stream 0+    soc <- liftIO $ socket AF_UNIX Stream 0     getSocketPath >>= \case-        Nothing    -> putStrLn "Failed to get i3 socket path" >> exitFailure+        Nothing    -> throwM $ ConnectException "Failed to get i3 socket path"         Just addr' -> do-            connect soc (SockAddrUnix $ BL.unpack addr')+            liftIO $ connect soc (SockAddrUnix $ BL.unpack addr')             pure soc +-- | Connect to SWAY socket and return it+connectsway :: (MonadThrow m, MonadIO m) => m Socket+connectsway = do+    soc <- liftIO $ socket AF_UNIX Stream 0+    getSwaySocketPath >>= \case+        Nothing    -> throwM $ ConnectException "Failed to get i3 socket path"+        Just addr' -> do+            liftIO $ connect soc (SockAddrUnix $ BL.unpack addr')+            pure soc+ -- | Useful for when you are receiving Events or Messages. data Response = Message MsgReply | Event Evt.Event deriving (Show, Eq)  -- | Get and parse the response using i3's IPC-getReply :: Socket -> IO (Either String (Int, BL.ByteString))+getReply :: MonadIO m => Socket -> m (Either String (Int, BL.ByteString)) getReply soc = do-    magic <- recv soc 6+    magic <- liftIO $ recv soc 6     if magic == "i3-ipc"         then do-            len  <- fromIntegral . runGet getWord32le <$> recv soc 4-            ty   <- fromIntegral . runGet getWord32le <$> recv soc 4-            body <- recv soc len-            pure $ Right (ty, body)+            len  <- getInt <$> liftIO (recv soc 4)+            ty   <- getInt <$> liftIO (recv soc 4)+            body <- liftIO $ recv soc len+            pure $ Right (fromIntegral ty, body)         else pure $ Left "Failed to get reply"+    where getInt = fromIntegral . runGet getWord32le  test :: Int -> BL.ByteString -> IO Int test ty body = do@@ -162,7 +194,7 @@     pure ty  -- | Parse response from socket, returning either an error or a 'I3IPC.Response', representing a sum type of a 'I3IPC.Reply.MsgReply' or 'I3IPC.Event.Event'-receive :: Socket -> IO (Either String Response)+receive :: MonadIO m => Socket -> m (Either String Response) receive soc = do     reply <- getReply soc     case reply of@@ -172,7 +204,7 @@         _ -> pure $ Left "Get Reply failed"  -- | Like receive but strict-- will use eitherDecode' under the hood to parse-receive' :: Socket -> IO (Either String Response)+receive' :: MonadIO m => Socket -> m (Either String Response) receive' soc = do     reply <- getReply soc     case reply of@@ -182,7 +214,7 @@         _ -> pure $ Left "Get Reply failed"  -- | Receive but specifically for msgs, for when you know the response won't include any Events-receiveMsg :: Socket -> IO (Either String MsgReply)+receiveMsg :: MonadIO m => Socket -> m (Either String MsgReply) receiveMsg soc = do     r <- getReply soc     pure $ do@@ -190,7 +222,7 @@         toMsgReply ty body  -- | Like 'I3IPC.receiveMsg' but strict-- uses eitherDecode'-receiveMsg' :: Socket -> IO (Either String MsgReply)+receiveMsg' :: MonadIO m => Socket -> m (Either String MsgReply) receiveMsg' soc = do     r <- getReply soc     pure $ do@@ -198,7 +230,7 @@         toMsgReply' ty body  -- | 'I3IPC.receive' specifically for Event-receiveEvent :: Socket -> IO (Either String Evt.Event)+receiveEvent :: MonadIO m => Socket -> m (Either String Evt.Event) receiveEvent soc = do     r <- getReply soc     pure $ do@@ -206,7 +238,7 @@         Evt.toEvent (ty `clearBit` 31) body  -- | like 'receiveEvent' but strict-- uses eitherDecode'-receiveEvent' :: Socket -> IO (Either String Evt.Event)+receiveEvent' :: MonadIO m => Socket -> m (Either String Evt.Event) receiveEvent' soc = do     r <- getReply soc     pure $ do@@ -220,37 +252,38 @@ -- Or, if there is no message body: -- -- > Msg.sendMsg soc Msg.X >> receiveMsg soc-runCommand :: Socket -> BL.ByteString -> IO (Either String MsgReply)+runCommand :: MonadIO m => Socket -> BL.ByteString -> m (Either String MsgReply) runCommand soc b = Msg.sendMsgPayload soc Msg.RunCommand b >> receiveMsg soc -runCommand' :: Socket -> BL.ByteString -> IO (Either String MsgReply)+runCommand'+    :: MonadIO m => Socket -> BL.ByteString -> m (Either String MsgReply) runCommand' soc b = Msg.sendMsgPayload soc Msg.RunCommand b >> receiveMsg' soc -getWorkspaces :: Socket -> IO (Either String MsgReply)+getWorkspaces :: MonadIO m => Socket -> m (Either String MsgReply) getWorkspaces soc = Msg.sendMsg soc Msg.Workspaces >> receiveMsg soc -getWorkspaces' :: Socket -> IO (Either String MsgReply)+getWorkspaces' :: MonadIO m => Socket -> m (Either String MsgReply) getWorkspaces' soc = Msg.sendMsg soc Msg.Workspaces >> receiveMsg' soc -getOutputs :: Socket -> IO (Either String MsgReply)+getOutputs :: MonadIO m => Socket -> m (Either String MsgReply) getOutputs soc = Msg.sendMsg soc Msg.Outputs >> receiveMsg soc -getOutputs' :: Socket -> IO (Either String MsgReply)+getOutputs' :: MonadIO m => Socket -> m (Either String MsgReply) getOutputs' soc = Msg.sendMsg soc Msg.Outputs >> receiveMsg' soc -getTree :: Socket -> IO (Either String MsgReply)+getTree :: MonadIO m => Socket -> m (Either String MsgReply) getTree soc = Msg.sendMsg soc Msg.Tree >> receiveMsg soc -getTree' :: Socket -> IO (Either String MsgReply)+getTree' :: MonadIO m => Socket -> m (Either String MsgReply) getTree' soc = Msg.sendMsg soc Msg.Tree >> receiveMsg' soc -getMarks :: Socket -> IO (Either String MsgReply)+getMarks :: MonadIO m => Socket -> m (Either String MsgReply) getMarks soc = Msg.sendMsg soc Msg.Marks >> receiveMsg soc -getMarks' :: Socket -> IO (Either String MsgReply)+getMarks' :: MonadIO m => Socket -> m (Either String MsgReply) getMarks' soc = Msg.sendMsg soc Msg.Marks >> receiveMsg' soc -getBarIds :: Socket -> IO (Either String BarIds)+getBarIds :: MonadIO m => Socket -> m (Either String BarIds) getBarIds soc = do     _ <- Msg.sendMsg soc Msg.BarConfig     r <- getReply soc@@ -259,41 +292,43 @@         decodeBarIds (snd body)  -- | Get a bar's config based on it's id-getBarConfig :: Socket -> BL.ByteString -> IO (Either String MsgReply)+getBarConfig+    :: MonadIO m => Socket -> BL.ByteString -> m (Either String MsgReply) getBarConfig soc b = Msg.sendMsgPayload soc Msg.BarConfig b >> receiveMsg' soc  -- | Like 'I3IPC.getBarConfig' but strict-getBarConfig' :: Socket -> BL.ByteString -> IO (Either String MsgReply)+getBarConfig'+    :: MonadIO m => Socket -> BL.ByteString -> m (Either String MsgReply) getBarConfig' soc b = Msg.sendMsgPayload soc Msg.BarConfig b >> receiveMsg' soc -getVersion :: Socket -> IO (Either String MsgReply)+getVersion :: MonadIO m => Socket -> m (Either String MsgReply) getVersion soc = Msg.sendMsg soc Msg.Version >> receiveMsg soc -getVersion' :: Socket -> IO (Either String MsgReply)+getVersion' :: MonadIO m => Socket -> m (Either String MsgReply) getVersion' soc = Msg.sendMsg soc Msg.Version >> receiveMsg' soc -getBindingModes :: Socket -> IO (Either String MsgReply)+getBindingModes :: MonadIO m => Socket -> m (Either String MsgReply) getBindingModes soc = Msg.sendMsg soc Msg.BindingModes >> receiveMsg soc -getBindingModes' :: Socket -> IO (Either String MsgReply)+getBindingModes' :: MonadIO m => Socket -> m (Either String MsgReply) getBindingModes' soc = Msg.sendMsg soc Msg.BindingModes >> receiveMsg' soc -getConfig :: Socket -> IO (Either String MsgReply)+getConfig :: MonadIO m => Socket -> m (Either String MsgReply) getConfig soc = Msg.sendMsg soc Msg.Config >> receiveMsg soc -getConfig' :: Socket -> IO (Either String MsgReply)+getConfig' :: MonadIO m => Socket -> m (Either String MsgReply) getConfig' soc = Msg.sendMsg soc Msg.Config >> receiveMsg' soc -getTick :: Socket -> IO (Either String MsgReply)+getTick :: MonadIO m => Socket -> m (Either String MsgReply) getTick soc = Msg.sendMsg soc Msg.Tick >> receiveMsg soc -getTick' :: Socket -> IO (Either String MsgReply)+getTick' :: MonadIO m => Socket -> m (Either String MsgReply) getTick' soc = Msg.sendMsg soc Msg.Tick >> receiveMsg' soc -getSync :: Socket -> IO (Either String MsgReply)+getSync :: MonadIO m => Socket -> m (Either String MsgReply) getSync soc = Msg.sendMsg soc Msg.Sync >> receiveMsg soc -getSync' :: Socket -> IO (Either String MsgReply)+getSync' :: MonadIO m => Socket -> m (Either String MsgReply) getSync' soc = Msg.sendMsg soc Msg.Sync >> receiveMsg' soc  @@ -305,9 +340,10 @@ -- > import qualified I3IPC.Subscribe               as Sub -- > import           I3IPC.Event -- > import           I3IPC                          ( subscribe )+-- > import           Control.Monad.IO.Class  -- >  -- > main :: IO ()--- > main = subscribe handle [Sub.Workspace, Sub.Window]+-- > main = liftIO $ subscribe handle [Sub.Workspace, Sub.Window] -- >  where -- >   handle :: Either String Event -> IO () -- >   handle (Right evt) = case evt of@@ -324,10 +360,11 @@ -- > import           I3IPC              ( connecti3 -- >                                     , getWorkspaces -- >                                     )+-- > import           Control.Monad.IO.Class  -- >  -- > main :: IO () -- > main = do--- >     soc <- connecti3+-- >     soc <- liftIO $ connecti3 -- >     print getWorkspaces --  -- $func@@ -340,8 +377,9 @@ -- > import           I3IPC              ( connecti3 -- >                                     , receiveMsg -- >                                     )+-- > import           Control.Monad.IO.Class  -- >  -- > main :: IO () -- > main = do--- >     soc <- connecti3+-- >     soc <- liftIO $ connecti3 -- >     print $ Msg.sendMsg soc Msg.Workspaces >> receiveMsg soc
src/I3IPC/Event.hs view
@@ -40,7 +40,7 @@ toEvent' :: Int -> BL.ByteString -> Either String Event toEvent' 0 = (Workspace <$>) . eitherDecode' toEvent' 1 = (Output <$>) . eitherDecode'-toEvent' 2 = (Mode <$>) . eitherDecode' +toEvent' 2 = (Mode <$>) . eitherDecode' toEvent' 3 = (Window <$>) . eitherDecode' toEvent' 4 = (BarConfigUpdate <$>) . eitherDecode' toEvent' 5 = (Binding <$>) . eitherDecode'@@ -68,18 +68,20 @@     | Reload     | Restored     | Move+    | UnknownChange     deriving (Eq, Generic, Show)  instance ToJSON WorkspaceChange where     toEncoding = \case-        Focus    -> text "focus"-        Init     -> text "init"-        Empty    -> text "empty"-        Urgent   -> text "urgent"-        Rename   -> text "rename"-        Reload   -> text "reload"-        Restored -> text "restored"-        Move     -> text "move"+        Focus         -> text "focus"+        Init          -> text "init"+        Empty         -> text "empty"+        Urgent        -> text "urgent"+        Rename        -> text "rename"+        Reload        -> text "reload"+        Restored      -> text "restored"+        Move          -> text "move"+        UnknownChange -> text "unknown"  instance FromJSON WorkspaceChange where     parseJSON (String s) = pure $! case s of@@ -91,7 +93,7 @@         "reload"   -> Reload         "restored" -> Restored         "move"     -> Move-        _          -> error "Received unrecognized WorkspaceChange"+        _          -> UnknownChange     parseJSON _ = mzero  -- | Workspace Event@@ -168,6 +170,7 @@     | WinFloating -- ^ the window has transitioned to or from floating      | WinUrgent -- ^ the window has become urgent or lost its urgent status      | WinMark -- ^ a mark has been added to or removed from the window +    | WinUnknown -- ^ an unknown change, submit a PR to add a new WindowChange if you get this     deriving (Eq, Show, Generic)  instance ToJSON WindowChange where@@ -181,6 +184,7 @@         WinUrgent         -> text "urgent"         WinMark           -> text "mark"         WinClose          -> text "close"+        WinUnknown        -> text "unknown"  instance FromJSON WindowChange where     parseJSON (String s) = pure $! case s of@@ -193,7 +197,7 @@         "urgent"          -> WinUrgent         "mark"            -> WinMark         "close"           -> WinClose-        _                 -> error "Received unrecognized WorkspaceChange"+        _                 -> WinUnknown     parseJSON _ = mzero  
src/I3IPC/Message.hs view
@@ -7,6 +7,7 @@     ) where +import           Control.Monad.IO.Class import           Network.Socket.ByteString.Lazy import           Network.Socket                      ( Socket ) import qualified Data.ByteString.Lazy               as BSL@@ -60,9 +61,11 @@     putWord32host $ fromIntegral (fromEnum msgtype)  -- | Send a message over the socket of 'MessageType' and some content-sendMsgPayload :: Socket -> MessageType -> BSL.ByteString -> IO Int64-sendMsgPayload soc msgtype msg = createMsgPayload msgtype msg & send soc+sendMsgPayload+    :: MonadIO m => Socket -> MessageType -> BSL.ByteString -> m Int64+sendMsgPayload soc msgtype msg =+    liftIO $ createMsgPayload msgtype msg & send soc  -- | Similar to 'sendMsg' but with no message body-sendMsg :: Socket -> MessageType -> IO Int64-sendMsg soc msgtype = createMsg msgtype & send soc+sendMsg :: MonadIO m => Socket -> MessageType -> m Int64+sendMsg soc msgtype = liftIO $ createMsg msgtype & send soc
src/I3IPC/Reply.hs view
@@ -161,6 +161,7 @@     Horizontal     | Vertical     | OrientNone+    | UnknownOrient     deriving (Eq, Generic, Show)  instance FromJSON NodeOrientation where@@ -168,14 +169,15 @@         "none"       -> OrientNone         "horizontal" -> Horizontal         "vertical"   -> Vertical-        _            -> error "Unrecognized NodeOrientation"+        _            -> UnknownOrient     parseJSON _ = mzero  instance ToJSON NodeOrientation where     toEncoding = \case-        OrientNone -> text "none"-        Vertical   -> text "vertical"-        Horizontal -> text "horizontal"+        OrientNone    -> text "none"+        Vertical      -> text "vertical"+        Horizontal    -> text "horizontal"+        UnknownOrient -> text "unknown"  instance ToJSON Node where     toEncoding =@@ -190,6 +192,7 @@     | Class     | WindowRole     | TransientFor+    | UnknownProperty     deriving (Eq, Enum, Ord, Generic, Show)  instance FromJSONKey WindowProperty where@@ -201,23 +204,25 @@             "class"         -> Class             "window_role"   -> WindowRole             "transient_for" -> TransientFor-            _               -> error "Unrecognized window property"+            _               -> UnknownProperty  instance ToJSONKey WindowProperty where     toJSONKey = ToJSONKeyText f g       where         f x = case x of-            Title        -> "title"-            Instance     -> "instance"-            Class        -> "class"-            WindowRole   -> "window_role"-            TransientFor -> "transient_for"+            Title           -> "title"+            Instance        -> "instance"+            Class           -> "class"+            WindowRole      -> "window_role"+            TransientFor    -> "transient_for"+            UnknownProperty -> "unknown"         g x = case x of-            Title        -> text "title"-            Instance     -> text "instance"-            Class        -> text "class"-            WindowRole   -> text "window_role"-            TransientFor -> text "transient_for"+            Title           -> text "title"+            Instance        -> text "instance"+            Class           -> text "class"+            WindowRole      -> text "window_role"+            TransientFor    -> text "transient_for"+            UnknownProperty -> text "unknown"  instance FromJSON WindowProperty where     parseJSON (String s) = pure $! case s of@@ -226,17 +231,18 @@         "class"         -> Class         "window_role"   -> WindowRole         "transient_for" -> TransientFor-        _               -> error "Unrecognized WindowProperty variant found"+        _               -> UnknownProperty     parseJSON _ = mzero   instance ToJSON WindowProperty where     toEncoding = \case-        Title        -> text "title"-        Instance     -> text "instance"-        Class        -> text "class"-        WindowRole   -> text "window_role"-        TransientFor -> text "transient_for"+        Title           -> text "title"+        Instance        -> text "instance"+        Class           -> text "class"+        WindowRole      -> text "window_role"+        TransientFor    -> text "transient_for"+        UnknownProperty -> text "unknown"  -- | Marks Reply -- The reply consists of a single array of strings for each container that has a mark. A mark can only be set on one container, so the array is unique. The order of that array is undefined.@@ -250,20 +256,22 @@     Normal     | None     | Pixel+    | UnknownBorder     deriving (Eq, Generic, Show)  instance ToJSON NodeBorder where     toEncoding = \case-        Normal -> text "normal"-        None   -> text "none"-        Pixel  -> text "pixel"+        Normal        -> text "normal"+        None          -> text "none"+        Pixel         -> text "pixel"+        UnknownBorder -> text "unknown"  instance FromJSON NodeBorder where     parseJSON (String s) = pure $! case s of         "normal" -> Normal         "none"   -> None         "pixel"  -> Pixel-        _        -> error "Unrecognized NodeBorder found"+        _        -> UnknownBorder     parseJSON _ = mzero  data Rect = Rect {@@ -283,6 +291,7 @@     | FloatingConType     | WorkspaceType     | DockAreaType+    | UnknownType     deriving (Eq, Generic, Show)  instance ToJSON NodeType where@@ -293,6 +302,7 @@         FloatingConType -> text "floating_con"         WorkspaceType   -> text "workspace"         DockAreaType    -> text "dockarea"+        UnknownType     -> text "unknown"  instance FromJSON NodeType where     parseJSON (String s) = pure $! case s of@@ -302,7 +312,7 @@         "floating_con" -> FloatingConType         "workspace"    -> WorkspaceType         "dockarea"     -> DockAreaType-        _              -> error "Received unrecognized NodeType"+        _              -> UnknownType     parseJSON _ = mzero  data NodeLayout =@@ -312,6 +322,7 @@     | TabbedLayout     | DockAreaLayout     | OutputLayout+    | UnknownLayout     deriving (Eq, Generic, Show)  instance ToJSON NodeLayout where@@ -322,6 +333,7 @@         TabbedLayout          -> text "tabbed"         DockAreaLayout        -> text "dockarea"         OutputLayout          -> text "output"+        UnknownLayout         -> text "unknown"  instance FromJSON NodeLayout where     parseJSON (String s) = pure $! case s of@@ -331,7 +343,7 @@         "tabbed"   -> TabbedLayout         "dockarea" -> DockAreaLayout         "output"   -> OutputLayout-        _          -> error "Received unrecognized NodeLayout"+        _          -> UnknownLayout     parseJSON _ = mzero  -- | BarConfig Reply@@ -385,6 +397,7 @@     | BindingModeText     | BindingModeBg     | BindingModeBorder+    | UnknownBarPart     deriving (Eq, Enum, Ord, Generic, Show, FromJSONKey)  instance ToJSONKey BarPart where@@ -412,6 +425,7 @@             BindingModeText         -> "binding_mode_text"             BindingModeBg           -> "binding_mode_bg"             BindingModeBorder       -> "binding_mode_border"+            UnknownBarPart          -> "unknown"         g x = case x of             Background              -> text "background"             Statusline              -> text "statusline"@@ -434,6 +448,7 @@             BindingModeText         -> text "binding_mode_text"             BindingModeBg           -> text "binding_mode_bg"             BindingModeBorder       -> text "binding_mode_border"+            UnknownBarPart          -> text "unknown"  instance FromJSON BarPart where     parseJSON (String s) = pure $! case s of@@ -458,7 +473,7 @@         "binding_mode_text"         -> BindingModeText         "binding_mode_bg"           -> BindingModeBg         "binding_mode_border"       -> BindingModeBorder-        _ -> error "Unrecognized BarPart variant found"+        _                           -> UnknownBarPart     parseJSON _ = mzero  instance ToJSON BarPart where@@ -484,6 +499,7 @@         BindingModeText         -> text "binding_mode_text"         BindingModeBg           -> text "binding_mode_bg"         BindingModeBorder       -> text "binding_mode_border"+        UnknownBarPart          -> text "unknown"  -- | Version Reply data VersionReply = VersionReply {
+ test/event/winevent.json view
@@ -0,0 +1,57 @@+{+  "change": "focus",+  "container": {+    "id": 94131357905936,+    "type": "con",+    "orientation": "none",+    "scratchpad_state": "none",+    "percent": 0.5,+    "urgent": false,+    "focused": true,+    "output": "DP-4.8",+    "layout": "splith",+    "workspace_layout": "default",+    "last_split_layout": "splith",+    "border": "pixel",+    "current_border_width": 3,+    "rect": {+      "x": 3207,+      "y": 54,+      "width": 1249,+      "height": 1362+    },+    "deco_rect": {+      "x": 0,+      "y": 0,+      "width": 0,+      "height": 0+    },+    "window_rect": {+      "x": 3,+      "y": 3,+      "width": 1243,+      "height": 1356+    },+    "geometry": {+      "x": 0,+      "y": 0,+      "width": 980,+      "height": 435+    },+    "name": "i3ipc: stack ghci",+    "window": 33554442,+    "window_properties": {+      "class": "URxvt",+      "instance": "urxvt",+      "title": "i3ipc: stack ghci",+      "transient_for": null+    },+    "nodes": [],+    "floating_nodes": [],+    "focus": [],+    "fullscreen_mode": 0,+    "sticky": false,+    "floating": "auto_off",+    "swallows": []+  }+}
+ test/event/workspace.json view
@@ -0,0 +1,204 @@+{+  "change": "focus",+  "current": {+    "id": 106996225489920,+    "type": "workspace",+    "orientation": "horizontal",+    "scratchpad_state": "none",+    "percent": null,+    "urgent": false,+    "focused": false,+    "output": "DVI-I-3",+    "layout": "splith",+    "workspace_layout": "default",+    "last_split_layout": "splith",+    "border": "pixel",+    "current_border_width": -1,+    "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+    "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "name": "9",+    "num": 9,+    "gaps": { "inner": 0, "outer": 0 },+    "window": null,+    "nodes": [+      {+        "id": 106996225488640,+        "type": "con",+        "orientation": "none",+        "scratchpad_state": "none",+        "percent": 0.5,+        "urgent": false,+        "focused": true,+        "output": "DVI-I-3",+        "layout": "splith",+        "workspace_layout": "default",+        "last_split_layout": "splith",+        "border": "pixel",+        "current_border_width": 1,+        "rect": { "x": 2572, "y": 41, "width": 1261, "height": 1547 },+        "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+        "window_rect": { "x": 1, "y": 1, "width": 1259, "height": 1545 },+        "geometry": { "x": 3328, "y": 416, "width": 1024, "height": 768 },+        "name": "I3IPC.hs - i3ipc - Visual Studio Code",+        "window": 62915005,+        "window_properties": {+          "class": "Code",+          "instance": "code",+          "window_role": "browser-window",+          "title": "I3IPC.hs - i3ipc - Visual Studio Code",+          "transient_for": null+        },+        "nodes": [],+        "floating_nodes": [],+        "focus": [],+        "fullscreen_mode": 0,+        "sticky": false,+        "floating": "auto_off",+        "swallows": []+      },+      {+        "id": 106996225489280,+        "type": "con",+        "orientation": "none",+        "scratchpad_state": "none",+        "percent": 0.5,+        "urgent": false,+        "focused": false,+        "output": "DVI-I-3",+        "layout": "splith",+        "workspace_layout": "default",+        "last_split_layout": "splith",+        "border": "pixel",+        "current_border_width": 1,+        "rect": { "x": 3847, "y": 41, "width": 1261, "height": 1547 },+        "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+        "window_rect": { "x": 1, "y": 1, "width": 1259, "height": 1545 },+        "geometry": { "x": 0, "y": 0, "width": 860, "height": 460 },+        "name": "i3ipc: stack ghci",+        "window": 58720329,+        "window_properties": {+          "class": "URxvt",+          "instance": "urxvt",+          "title": "i3ipc: stack ghci",+          "transient_for": null+        },+        "nodes": [],+        "floating_nodes": [],+        "focus": [],+        "fullscreen_mode": 0,+        "sticky": false,+        "floating": "auto_off",+        "swallows": []+      }+    ],+    "floating_nodes": [],+    "focus": [106996225488640, 106996225489280],+    "fullscreen_mode": 1,+    "sticky": false,+    "floating": "auto_off",+    "swallows": []+  },+  "old": {+    "id": 106996225529600,+    "type": "workspace",+    "orientation": "horizontal",+    "scratchpad_state": "none",+    "percent": 0.5,+    "urgent": false,+    "focused": false,+    "output": "DVI-I-2",+    "layout": "splith",+    "workspace_layout": "default",+    "last_split_layout": "splith",+    "border": "pixel",+    "current_border_width": -1,+    "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+    "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+    "name": "4",+    "num": 4,+    "gaps": { "inner": 0, "outer": 0 },+    "window": null,+    "nodes": [+      {+        "id": 106996225528960,+        "type": "con",+        "orientation": "none",+        "scratchpad_state": "none",+        "percent": 0.30000000000000004441,+        "urgent": false,+        "focused": false,+        "output": "DVI-I-2",+        "layout": "splith",+        "workspace_layout": "default",+        "last_split_layout": "splith",+        "border": "pixel",+        "current_border_width": 1,+        "rect": { "x": 12, "y": 42, "width": 749, "height": 1546 },+        "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+        "window_rect": { "x": 1, "y": 1, "width": 747, "height": 1544 },+        "geometry": { "x": 0, "y": 0, "width": 747, "height": 1544 },+        "name": "Inbox - evan@vectorface.com - Mozilla Thunderbird",+        "window": 73400336,+        "window_properties": {+          "class": "Thunderbird",+          "instance": "Mail",+          "window_role": "3pane",+          "title": "Inbox - evan@vectorface.com - Mozilla Thunderbird",+          "transient_for": null+        },+        "nodes": [],+        "floating_nodes": [],+        "focus": [],+        "fullscreen_mode": 0,+        "sticky": false,+        "floating": "auto_off",+        "swallows": []+      },+      {+        "id": 106996225528320,+        "type": "con",+        "orientation": "none",+        "scratchpad_state": "none",+        "percent": 0.69999999999999995559,+        "urgent": false,+        "focused": false,+        "output": "DVI-I-2",+        "layout": "splith",+        "workspace_layout": "default",+        "last_split_layout": "splith",+        "border": "pixel",+        "current_border_width": 1,+        "rect": { "x": 775, "y": 42, "width": 1773, "height": 1546 },+        "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+        "window_rect": { "x": 1, "y": 1, "width": 1771, "height": 1544 },+        "geometry": { "x": 0, "y": 0, "width": 1771, "height": 1544 },+        "name": "haskell - No output will be generated because there is no Main module - Stack Overflow - Mozilla Firefox",+        "window": 31457296,+        "window_properties": {+          "class": "Firefox",+          "instance": "Navigator",+          "window_role": "browser",+          "title": "haskell - No output will be generated because there is no Main module - Stack Overflow - Mozilla Firefox",+          "transient_for": null+        },+        "nodes": [],+        "floating_nodes": [],+        "focus": [],+        "fullscreen_mode": 0,+        "sticky": false,+        "floating": "auto_off",+        "swallows": []+      }+    ],+    "floating_nodes": [],+    "focus": [106996225528320, 106996225528960],+    "fullscreen_mode": 1,+    "sticky": false,+    "floating": "auto_off",+    "swallows": []+  }+}
+ test/reply/output.json view
@@ -0,0 +1,23 @@+[+  {+    "name": "xroot-0",+    "active": false,+    "primary": false,+    "rect": { "x": 0, "y": 0, "width": 5120, "height": 1600 },+    "current_workspace": null+  },+  {+    "name": "DVI-I-2",+    "active": true,+    "primary": false,+    "rect": { "x": 0, "y": 0, "width": 2560, "height": 1600 },+    "current_workspace": "1"+  },+  {+    "name": "DVI-I-3",+    "active": true,+    "primary": false,+    "rect": { "x": 2560, "y": 0, "width": 2560, "height": 1600 },+    "current_workspace": "7"+  }+]
+ test/reply/tree.json view
@@ -0,0 +1,1249 @@+{+  "id": 106996225351040,+  "type": "root",+  "orientation": "horizontal",+  "scratchpad_state": "none",+  "percent": null,+  "urgent": false,+  "focused": false,+  "layout": "splith",+  "workspace_layout": "default",+  "last_split_layout": "splith",+  "border": "pixel",+  "current_border_width": -1,+  "rect": { "x": 0, "y": 0, "width": 5120, "height": 1600 },+  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+  "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+  "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+  "name": "root",+  "window": null,+  "nodes": [+    {+      "id": 106996225350400,+      "type": "output",+      "orientation": "none",+      "scratchpad_state": "none",+      "percent": 0.33333333333333331483,+      "urgent": false,+      "focused": false,+      "layout": "output",+      "workspace_layout": "default",+      "last_split_layout": "splith",+      "border": "pixel",+      "current_border_width": -1,+      "rect": { "x": 0, "y": 0, "width": 2560, "height": 1600 },+      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "name": "__i3",+      "window": null,+      "nodes": [+        {+          "id": 106996225349760,+          "type": "con",+          "orientation": "horizontal",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "__i3",+          "layout": "splith",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "content",+          "window": null,+          "nodes": [+            {+              "id": 106996225349120,+              "type": "workspace",+              "orientation": "none",+              "scratchpad_state": "none",+              "percent": null,+              "urgent": false,+              "focused": false,+              "output": "__i3",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "__i3_scratch",+              "num": -1,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [],+              "floating_nodes": [],+              "focus": [],+              "fullscreen_mode": 1,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            }+          ],+          "floating_nodes": [],+          "focus": [106996225349120],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": []+        }+      ],+      "floating_nodes": [],+      "focus": [106996225349760],+      "fullscreen_mode": 0,+      "sticky": false,+      "floating": "auto_off",+      "swallows": []+    },+    {+      "id": 106996225345920,+      "type": "output",+      "orientation": "none",+      "scratchpad_state": "none",+      "percent": 0.33333333333333331483,+      "urgent": false,+      "focused": false,+      "layout": "output",+      "workspace_layout": "default",+      "last_split_layout": "splith",+      "border": "pixel",+      "current_border_width": -1,+      "rect": { "x": 2560, "y": 0, "width": 2560, "height": 1600 },+      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "name": "DVI-I-3",+      "window": null,+      "nodes": [+        {+          "id": 106996225345280,+          "type": "dockarea",+          "orientation": "none",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-3",+          "layout": "dockarea",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 2560, "y": 0, "width": 2560, "height": 29 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "topdock",+          "window": null,+          "nodes": [+            {+              "id": 106996225536000,+              "type": "con",+              "orientation": "none",+              "scratchpad_state": "none",+              "percent": 1.0,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-3",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": 3,+              "rect": { "x": 2560, "y": 0, "width": 2560, "height": 29 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 2560, "height": 29 },+              "geometry": { "x": 2560, "y": 0, "width": 2560, "height": 29 },+              "name": "polybar-dvi3_DVI-I-3",+              "window": 23068674,+              "window_properties": {+                "class": "Polybar",+                "instance": "polybar",+                "title": "polybar-dvi3_DVI-I-3",+                "transient_for": null+              },+              "nodes": [],+              "floating_nodes": [],+              "focus": [],+              "fullscreen_mode": 0,+              "sticky": true,+              "floating": "auto_off",+              "swallows": []+            }+          ],+          "floating_nodes": [],+          "focus": [106996225536000],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": [{ "dock": 2, "insert_where": 2 }]+        },+        {+          "id": 106996225344640,+          "type": "con",+          "orientation": "horizontal",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-3",+          "layout": "splith",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "content",+          "window": null,+          "nodes": [+            {+              "id": 106996225538560,+              "type": "workspace",+              "orientation": "vertical",+              "scratchpad_state": "none",+              "percent": 0.5,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-3",+              "layout": "splitv",+              "workspace_layout": "default",+              "last_split_layout": "splitv",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "2",+              "num": 2,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [+                {+                  "id": 106996225532160,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 1.0,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-3",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 0,+                    "y": 0,+                    "width": 2560,+                    "height": 1571+                  },+                  "geometry": {+                    "x": 3847,+                    "y": 41,+                    "width": 1259,+                    "height": 1545+                  },+                  "name": "Rust Track | Exercism - Mozilla Firefox",+                  "window": 31457330,+                  "window_properties": {+                    "class": "Firefox",+                    "instance": "Navigator",+                    "window_role": "browser",+                    "title": "Rust Track | Exercism - Mozilla Firefox",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                }+              ],+              "floating_nodes": [],+              "focus": [106996225532160],+              "fullscreen_mode": 0,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            },+            {+              "id": 106996225511680,+              "type": "workspace",+              "orientation": "horizontal",+              "scratchpad_state": "none",+              "percent": null,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-3",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "7",+              "num": 7,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [+                {+                  "id": 106996225509760,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 0.5,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-3",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 2572, "y": 41, "width": 1261, "height": 1547 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 1,+                    "y": 1,+                    "width": 1259,+                    "height": 1545+                  },+                  "geometry": {+                    "x": 3328,+                    "y": 416,+                    "width": 1024,+                    "height": 768+                  },+                  "name": "Reply.hs - i3ipc - Visual Studio Code",+                  "window": 62914600,+                  "window_properties": {+                    "class": "Code",+                    "instance": "code",+                    "window_role": "browser-window",+                    "title": "Reply.hs - i3ipc - Visual Studio Code",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                },+                {+                  "id": 106996225509120,+                  "type": "con",+                  "orientation": "vertical",+                  "scratchpad_state": "none",+                  "percent": 0.5,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-3",+                  "layout": "splitv",+                  "workspace_layout": "default",+                  "last_split_layout": "splitv",+                  "border": "pixel",+                  "current_border_width": -1,+                  "rect": { "x": 3840, "y": 29, "width": 1280, "height": 1571 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "name": null,+                  "window": null,+                  "nodes": [+                    {+                      "id": 106996225511040,+                      "type": "con",+                      "orientation": "none",+                      "scratchpad_state": "none",+                      "percent": 0.5,+                      "urgent": false,+                      "focused": true,+                      "output": "DVI-I-3",+                      "layout": "splith",+                      "workspace_layout": "default",+                      "last_split_layout": "splith",+                      "border": "pixel",+                      "current_border_width": 1,+                      "rect": {+                        "x": 3847,+                        "y": 41,+                        "width": 1261,+                        "height": 766+                      },+                      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "window_rect": {+                        "x": 1,+                        "y": 1,+                        "width": 1259,+                        "height": 764+                      },+                      "geometry": {+                        "x": 0,+                        "y": 0,+                        "width": 860,+                        "height": 460+                      },+                      "name": "i3ipc: stack ghci",+                      "window": 58720285,+                      "window_properties": {+                        "class": "URxvt",+                        "instance": "urxvt",+                        "title": "i3ipc: stack ghci",+                        "transient_for": null+                      },+                      "nodes": [],+                      "floating_nodes": [],+                      "focus": [],+                      "fullscreen_mode": 0,+                      "sticky": false,+                      "floating": "auto_off",+                      "swallows": []+                    },+                    {+                      "id": 106996225510400,+                      "type": "con",+                      "orientation": "none",+                      "scratchpad_state": "none",+                      "percent": 0.5,+                      "urgent": false,+                      "focused": false,+                      "output": "DVI-I-3",+                      "layout": "splith",+                      "workspace_layout": "default",+                      "last_split_layout": "splith",+                      "border": "pixel",+                      "current_border_width": 1,+                      "rect": {+                        "x": 3847,+                        "y": 821,+                        "width": 1261,+                        "height": 767+                      },+                      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "window_rect": {+                        "x": 1,+                        "y": 1,+                        "width": 1259,+                        "height": 765+                      },+                      "geometry": {+                        "x": 0,+                        "y": 0,+                        "width": 860,+                        "height": 460+                      },+                      "name": "~/personal/haskell/i3ipc",+                      "window": 58720305,+                      "window_properties": {+                        "class": "URxvt",+                        "instance": "urxvt",+                        "title": "~/personal/haskell/i3ipc",+                        "transient_for": null+                      },+                      "nodes": [],+                      "floating_nodes": [],+                      "focus": [],+                      "fullscreen_mode": 0,+                      "sticky": false,+                      "floating": "auto_off",+                      "swallows": []+                    }+                  ],+                  "floating_nodes": [],+                  "focus": [106996225511040, 106996225510400],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                }+              ],+              "floating_nodes": [],+              "focus": [106996225509120, 106996225509760],+              "fullscreen_mode": 1,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            },+            {+              "id": 106996225516160,+              "type": "workspace",+              "orientation": "horizontal",+              "scratchpad_state": "none",+              "percent": 0.5,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-3",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "8",+              "num": 8,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [+                {+                  "id": 106996225514880,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 0.5999999999999999778,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-3",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 2572, "y": 41, "width": 1517, "height": 1547 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 1,+                    "y": 1,+                    "width": 1515,+                    "height": 1545+                  },+                  "geometry": {+                    "x": 2573,+                    "y": 42,+                    "width": 1515,+                    "height": 1545+                  },+                  "name": "Game.js - unified-components - Visual Studio Code",+                  "window": 62914561,+                  "window_properties": {+                    "class": "Code",+                    "instance": "code",+                    "window_role": "browser-window",+                    "title": "Game.js - unified-components - Visual Studio Code",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                },+                {+                  "id": 106996225515520,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 0.4000000000000000222,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-3",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 4103, "y": 41, "width": 1005, "height": 1547 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 1,+                    "y": 1,+                    "width": 1003,+                    "height": 1545+                  },+                  "geometry": { "x": 0, "y": 0, "width": 860, "height": 460 },+                  "name": "~/dev/unified-components",+                  "window": 58720266,+                  "window_properties": {+                    "class": "URxvt",+                    "instance": "urxvt",+                    "title": "~/dev/unified-components",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                }+              ],+              "floating_nodes": [],+              "focus": [106996225514880, 106996225515520],+              "fullscreen_mode": 0,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            }+          ],+          "floating_nodes": [],+          "focus": [106996225511680, 106996225516160, 106996225538560],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": []+        },+        {+          "id": 106996225344000,+          "type": "dockarea",+          "orientation": "none",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-3",+          "layout": "dockarea",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 2560, "y": 1600, "width": 2560, "height": 0 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "bottomdock",+          "window": null,+          "nodes": [],+          "floating_nodes": [],+          "focus": [],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": [{ "dock": 3, "insert_where": 2 }]+        }+      ],+      "floating_nodes": [],+      "focus": [106996225344640, 106996225345280, 106996225344000],+      "fullscreen_mode": 0,+      "sticky": false,+      "floating": "auto_off",+      "swallows": []+    },+    {+      "id": 106996225348480,+      "type": "output",+      "orientation": "none",+      "scratchpad_state": "none",+      "percent": 0.33333333333333331483,+      "urgent": false,+      "focused": false,+      "layout": "output",+      "workspace_layout": "default",+      "last_split_layout": "splith",+      "border": "pixel",+      "current_border_width": -1,+      "rect": { "x": 0, "y": 0, "width": 2560, "height": 1600 },+      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+      "name": "DVI-I-2",+      "window": null,+      "nodes": [+        {+          "id": 106996225347840,+          "type": "dockarea",+          "orientation": "none",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-2",+          "layout": "dockarea",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 0, "y": 0, "width": 2560, "height": 30 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "topdock",+          "window": null,+          "nodes": [+            {+              "id": 106996225535360,+              "type": "con",+              "orientation": "none",+              "scratchpad_state": "none",+              "percent": 1.0,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-2",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": 3,+              "rect": { "x": 0, "y": 0, "width": 2560, "height": 30 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 2560, "height": 30 },+              "geometry": { "x": 0, "y": 0, "width": 2560, "height": 30 },+              "name": "polybar-dvi2_DVI-I-2",+              "window": 10485762,+              "window_properties": {+                "class": "Polybar",+                "instance": "polybar",+                "title": "polybar-dvi2_DVI-I-2",+                "transient_for": null+              },+              "nodes": [],+              "floating_nodes": [],+              "focus": [],+              "fullscreen_mode": 0,+              "sticky": true,+              "floating": "auto_off",+              "swallows": []+            }+          ],+          "floating_nodes": [],+          "focus": [106996225535360],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": [{ "dock": 2, "insert_where": 2 }]+        },+        {+          "id": 106996225347200,+          "type": "con",+          "orientation": "horizontal",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-2",+          "layout": "splith",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "content",+          "window": null,+          "nodes": [+            {+              "id": 106996225343360,+              "type": "workspace",+              "orientation": "horizontal",+              "scratchpad_state": "none",+              "percent": null,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-2",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "1",+              "num": 1,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [+                {+                  "id": 106996225525760,+                  "type": "con",+                  "orientation": "vertical",+                  "scratchpad_state": "none",+                  "percent": 0.25999999999999989786,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-2",+                  "layout": "splitv",+                  "workspace_layout": "default",+                  "last_split_layout": "splitv",+                  "border": "pixel",+                  "current_border_width": -1,+                  "rect": { "x": 0, "y": 30, "width": 666, "height": 1570 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "name": null,+                  "window": null,+                  "nodes": [+                    {+                      "id": 106996225518080,+                      "type": "con",+                      "orientation": "vertical",+                      "scratchpad_state": "none",+                      "percent": 0.5,+                      "urgent": false,+                      "focused": false,+                      "output": "DVI-I-2",+                      "layout": "stacked",+                      "workspace_layout": "default",+                      "last_split_layout": "splith",+                      "border": "pixel",+                      "current_border_width": -1,+                      "rect": { "x": 12, "y": 42, "width": 647, "height": 766 },+                      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "window_rect": {+                        "x": 0,+                        "y": 0,+                        "width": 0,+                        "height": 0+                      },+                      "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "name": null,+                      "window": null,+                      "nodes": [+                        {+                          "id": 106996225537920,+                          "type": "con",+                          "orientation": "none",+                          "scratchpad_state": "none",+                          "percent": 0.5,+                          "urgent": false,+                          "focused": false,+                          "output": "DVI-I-2",+                          "layout": "splith",+                          "workspace_layout": "default",+                          "last_split_layout": "splith",+                          "border": "pixel",+                          "current_border_width": 1,+                          "rect": {+                            "x": 12,+                            "y": 78,+                            "width": 647,+                            "height": 730+                          },+                          "deco_rect": {+                            "x": 0,+                            "y": 0,+                            "width": 647,+                            "height": 18+                          },+                          "window_rect": {+                            "x": 1,+                            "y": 0,+                            "width": 645,+                            "height": 729+                          },+                          "geometry": {+                            "x": 12,+                            "y": 42,+                            "width": 747,+                            "height": 1544+                          },+                          "name": "Buddy List",+                          "window": 20971615,+                          "window_properties": {+                            "class": "Pidgin",+                            "instance": "Pidgin",+                            "window_role": "buddy_list",+                            "title": "Buddy List",+                            "transient_for": null+                          },+                          "nodes": [],+                          "floating_nodes": [],+                          "focus": [],+                          "fullscreen_mode": 0,+                          "sticky": false,+                          "floating": "auto_off",+                          "swallows": []+                        },+                        {+                          "id": 106996225517440,+                          "type": "con",+                          "orientation": "none",+                          "scratchpad_state": "none",+                          "percent": 0.5,+                          "urgent": false,+                          "focused": false,+                          "output": "DVI-I-2",+                          "layout": "splith",+                          "workspace_layout": "default",+                          "last_split_layout": "splith",+                          "border": "pixel",+                          "current_border_width": 1,+                          "rect": {+                            "x": 12,+                            "y": 78,+                            "width": 647,+                            "height": 730+                          },+                          "deco_rect": {+                            "x": 0,+                            "y": 18,+                            "width": 647,+                            "height": 18+                          },+                          "window_rect": {+                            "x": 1,+                            "y": 0,+                            "width": 645,+                            "height": 729+                          },+                          "geometry": {+                            "x": 13,+                            "y": 78,+                            "width": 800,+                            "height": 1509+                          },+                          "name": "Signal",+                          "window": 48234497,+                          "window_properties": {+                            "class": "Signal",+                            "instance": "signal",+                            "window_role": "browser-window",+                            "title": "Signal",+                            "transient_for": null+                          },+                          "nodes": [],+                          "floating_nodes": [],+                          "focus": [],+                          "fullscreen_mode": 0,+                          "sticky": false,+                          "floating": "auto_off",+                          "swallows": []+                        }+                      ],+                      "floating_nodes": [],+                      "focus": [106996225537920, 106996225517440],+                      "fullscreen_mode": 0,+                      "sticky": false,+                      "floating": "auto_off",+                      "swallows": []+                    },+                    {+                      "id": 106996225526400,+                      "type": "con",+                      "orientation": "none",+                      "scratchpad_state": "none",+                      "percent": 0.5,+                      "urgent": false,+                      "focused": false,+                      "output": "DVI-I-2",+                      "layout": "splith",+                      "workspace_layout": "default",+                      "last_split_layout": "splith",+                      "border": "pixel",+                      "current_border_width": 1,+                      "rect": {+                        "x": 12,+                        "y": 822,+                        "width": 647,+                        "height": 766+                      },+                      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "window_rect": {+                        "x": 1,+                        "y": 1,+                        "width": 645,+                        "height": 764+                      },+                      "geometry": {+                        "x": 0,+                        "y": 0,+                        "width": 491,+                        "height": 753+                      },+                      "name": "Francis",+                      "window": 20974804,+                      "window_properties": {+                        "class": "Pidgin",+                        "instance": "Pidgin",+                        "window_role": "conversation",+                        "title": "Francis",+                        "transient_for": null+                      },+                      "nodes": [],+                      "floating_nodes": [],+                      "focus": [],+                      "fullscreen_mode": 0,+                      "sticky": false,+                      "floating": "auto_off",+                      "swallows": []+                    }+                  ],+                  "floating_nodes": [],+                  "focus": [106996225526400, 106996225518080],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                },+                {+                  "id": 106996225512960,+                  "type": "con",+                  "orientation": "vertical",+                  "scratchpad_state": "none",+                  "percent": 0.73999999999999999112,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-2",+                  "layout": "splitv",+                  "workspace_layout": "default",+                  "last_split_layout": "splitv",+                  "border": "pixel",+                  "current_border_width": -1,+                  "rect": { "x": 666, "y": 30, "width": 1894, "height": 1570 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "name": null,+                  "window": null,+                  "nodes": [+                    {+                      "id": 106996225532800,+                      "type": "con",+                      "orientation": "none",+                      "scratchpad_state": "none",+                      "percent": 1.0,+                      "urgent": false,+                      "focused": false,+                      "output": "DVI-I-2",+                      "layout": "splith",+                      "workspace_layout": "default",+                      "last_split_layout": "splith",+                      "border": "pixel",+                      "current_border_width": 1,+                      "rect": {+                        "x": 673,+                        "y": 42,+                        "width": 1875,+                        "height": 1546+                      },+                      "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                      "window_rect": {+                        "x": 1,+                        "y": 1,+                        "width": 1873,+                        "height": 1544+                      },+                      "geometry": {+                        "x": 775,+                        "y": 42,+                        "width": 1771,+                        "height": 1544+                      },+                      "name": "i3: IPC interface (interprocess communication) - Mozilla Firefox",+                      "window": 31457319,+                      "window_properties": {+                        "class": "Firefox",+                        "instance": "Navigator",+                        "window_role": "browser",+                        "title": "i3: IPC interface (interprocess communication) - Mozilla Firefox",+                        "transient_for": null+                      },+                      "nodes": [],+                      "floating_nodes": [],+                      "focus": [],+                      "fullscreen_mode": 0,+                      "sticky": false,+                      "floating": "auto_off",+                      "swallows": []+                    }+                  ],+                  "floating_nodes": [],+                  "focus": [106996225532800],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                }+              ],+              "floating_nodes": [],+              "focus": [106996225512960, 106996225525760],+              "fullscreen_mode": 1,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            },+            {+              "id": 106996225531520,+              "type": "workspace",+              "orientation": "horizontal",+              "scratchpad_state": "none",+              "percent": null,+              "urgent": false,+              "focused": false,+              "output": "DVI-I-2",+              "layout": "splith",+              "workspace_layout": "default",+              "last_split_layout": "splith",+              "border": "pixel",+              "current_border_width": -1,+              "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+              "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+              "name": "4",+              "num": 4,+              "gaps": { "inner": 0, "outer": 0 },+              "window": null,+              "nodes": [+                {+                  "id": 106996225513600,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 0.30000000000000004441,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-2",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 12, "y": 42, "width": 749, "height": 1546 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 1,+                    "y": 1,+                    "width": 747,+                    "height": 1544+                  },+                  "geometry": { "x": 0, "y": 0, "width": 747, "height": 1544 },+                  "name": "Inbox - evan@vectorface.com - Mozilla Thunderbird",+                  "window": 73400336,+                  "window_properties": {+                    "class": "Thunderbird",+                    "instance": "Mail",+                    "window_role": "3pane",+                    "title": "Inbox - evan@vectorface.com - Mozilla Thunderbird",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                },+                {+                  "id": 106996225533440,+                  "type": "con",+                  "orientation": "none",+                  "scratchpad_state": "none",+                  "percent": 0.69999999999999995559,+                  "urgent": false,+                  "focused": false,+                  "output": "DVI-I-2",+                  "layout": "splith",+                  "workspace_layout": "default",+                  "last_split_layout": "splith",+                  "border": "pixel",+                  "current_border_width": 1,+                  "rect": { "x": 775, "y": 42, "width": 1773, "height": 1546 },+                  "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+                  "window_rect": {+                    "x": 1,+                    "y": 1,+                    "width": 1771,+                    "height": 1544+                  },+                  "geometry": { "x": 0, "y": 0, "width": 1771, "height": 1544 },+                  "name": "Phil lobby gamelist p2 (!96) · Merge Requests · Client / unified-components · GitLab - Mozilla Firefox",+                  "window": 31457296,+                  "window_properties": {+                    "class": "Firefox",+                    "instance": "Navigator",+                    "window_role": "browser",+                    "title": "Phil lobby gamelist p2 (!96) · Merge Requests · Client / unified-components · GitLab - Mozilla Firefox",+                    "transient_for": null+                  },+                  "nodes": [],+                  "floating_nodes": [],+                  "focus": [],+                  "fullscreen_mode": 0,+                  "sticky": false,+                  "floating": "auto_off",+                  "swallows": []+                }+              ],+              "floating_nodes": [],+              "focus": [106996225513600, 106996225533440],+              "fullscreen_mode": 0,+              "sticky": false,+              "floating": "auto_off",+              "swallows": []+            }+          ],+          "floating_nodes": [],+          "focus": [106996225343360, 106996225531520],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": []+        },+        {+          "id": 106996225346560,+          "type": "dockarea",+          "orientation": "none",+          "scratchpad_state": "none",+          "percent": null,+          "urgent": false,+          "focused": false,+          "output": "DVI-I-2",+          "layout": "dockarea",+          "workspace_layout": "default",+          "last_split_layout": "splith",+          "border": "pixel",+          "current_border_width": -1,+          "rect": { "x": 0, "y": 1600, "width": 2560, "height": 0 },+          "deco_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "window_rect": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "geometry": { "x": 0, "y": 0, "width": 0, "height": 0 },+          "name": "bottomdock",+          "window": null,+          "nodes": [],+          "floating_nodes": [],+          "focus": [],+          "fullscreen_mode": 0,+          "sticky": false,+          "floating": "auto_off",+          "swallows": [{ "dock": 3, "insert_where": 2 }]+        }+      ],+      "floating_nodes": [],+      "focus": [106996225347200, 106996225347840, 106996225346560],+      "fullscreen_mode": 0,+      "sticky": false,+      "floating": "auto_off",+      "swallows": []+    }+  ],+  "floating_nodes": [],+  "focus": [106996225345920, 106996225348480, 106996225350400],+  "fullscreen_mode": 0,+  "sticky": false,+  "floating": "auto_off",+  "swallows": []+}
+ test/reply/version.json view
@@ -0,0 +1,7 @@+{+  "major": 4,+  "minor": 16,+  "patch": 0,+  "human_readable": "4.16-3-gecbfb2b4 (2018-11-04, branch \"gaps\")",+  "loaded_config_file_name": "/home/evan/.config/i3/config"+}
+ test/reply/workspace.json view
@@ -0,0 +1,47 @@+[+  {+    "num": 2,+    "name": "2",+    "visible": false,+    "focused": false,+    "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+    "output": "DVI-I-3",+    "urgent": false+  },+  {+    "num": 7,+    "name": "7",+    "visible": true,+    "focused": true,+    "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+    "output": "DVI-I-3",+    "urgent": false+  },+  {+    "num": 8,+    "name": "8",+    "visible": false,+    "focused": false,+    "rect": { "x": 2560, "y": 29, "width": 2560, "height": 1571 },+    "output": "DVI-I-3",+    "urgent": false+  },+  {+    "num": 1,+    "name": "1",+    "visible": true,+    "focused": false,+    "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+    "output": "DVI-I-2",+    "urgent": false+  },+  {+    "num": 4,+    "name": "4",+    "visible": false,+    "focused": false,+    "rect": { "x": 0, "y": 30, "width": 2560, "height": 1570 },+    "output": "DVI-I-2",+    "urgent": false+  }+]