aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2026-01-03 21:07:25 -0700
committerJosh Rahm <joshuarahm@gmail.com>2026-01-03 21:07:25 -0700
commita5965d7079be4454d343ffd3bff0c6b8c5d63abe (patch)
tree4026eb165ef9faced7acee095e5d247a138c2cbf
parent418d2b2b0829ed17e523867896ea321fc2b3a79b (diff)
downloadmontis-a5965d7079be4454d343ffd3bff0c6b8c5d63abe.tar.gz
montis-a5965d7079be4454d343ffd3bff0c6b8c5d63abe.tar.bz2
montis-a5965d7079be4454d343ffd3bff0c6b8c5d63abe.zip
[wip] rewrite plugin to a more scalable architecture.
-rw-r--r--plug/src/Config.hs94
-rw-r--r--plug/src/Lib.hs6
-rw-r--r--plug/src/Montis/Base/Foreign/Runtime.hs (renamed from plug/src/Montis/Foreign/Import.hs)4
-rw-r--r--plug/src/Montis/Base/Foreign/WlRoots.hs44
-rw-r--r--plug/src/Montis/Base/Foreign/WlRoots/Types.hs85
-rw-r--r--plug/src/Montis/Constraints.hs15
-rw-r--r--plug/src/Montis/Core.hs380
-rw-r--r--plug/src/Montis/Core/ButtonEvent.hs18
-rw-r--r--plug/src/Montis/Core/Events.hs37
-rw-r--r--plug/src/Montis/Core/Extensions.hs29
-rw-r--r--plug/src/Montis/Core/Internal/Foreign/Export.hs85
-rw-r--r--plug/src/Montis/Core/KeyEvent.hs22
-rw-r--r--plug/src/Montis/Core/Keys.hs240
-rw-r--r--plug/src/Montis/Core/Monad.hs84
-rw-r--r--plug/src/Montis/Core/Plugin/Interface.hs20
-rw-r--r--plug/src/Montis/Core/Runtime.hs26
-rw-r--r--plug/src/Montis/Core/Start.hs38
-rw-r--r--plug/src/Montis/Core/State.hs99
-rw-r--r--plug/src/Montis/Core/State/Marshal.hs39
-rw-r--r--plug/src/Montis/Core/SurfaceEvent.hs16
-rw-r--r--plug/src/Montis/Core/W.hs365
-rw-r--r--plug/src/Montis/Dsl/Bind.hs129
-rw-r--r--plug/src/Montis/Dsl/Buttons.hsc230
-rw-r--r--plug/src/Montis/Dsl/Input.hs284
-rw-r--r--plug/src/Montis/Foreign.hs18
-rw-r--r--plug/src/Montis/Foreign/Export.hs209
-rw-r--r--plug/src/Montis/Foreign/ForeignInterface.hs22
-rw-r--r--plug/src/Montis/Foreign/WlRoots.hs72
-rw-r--r--plug/src/Montis/Keys/Macros.hs145
-rw-r--r--plug/src/Montis/Keys/MagicModifierKey.hs51
-rw-r--r--plug/src/Montis/Layout/Combine.hs49
-rw-r--r--plug/src/Montis/Layout/Full.hs25
-rw-r--r--plug/src/Montis/StackSet.hs210
33 files changed, 583 insertions, 2607 deletions
diff --git a/plug/src/Config.hs b/plug/src/Config.hs
index 70920fd..0904ded 100644
--- a/plug/src/Config.hs
+++ b/plug/src/Config.hs
@@ -1,88 +1,18 @@
-module Config (config) where
+module Config () where
-import Control.Monad (unless)
-import Data.Bits
-import Data.Data (Proxy (Proxy))
-import Montis.Core.ButtonEvent as ButtonEvent
-import Montis.Core.KeyEvent as KeyEvent
-import Montis.Core.W
-import Montis.Dsl.Bind
-import Montis.Dsl.Input
-import Montis.Keys.Macros
-import Montis.Keys.MagicModifierKey
-import Montis.Layout.Full
-import Montis.Core.Runtime (requestHotReload)
+import Montis.Core
-config :: Config WindowLayout
-config =
- defaultConfig
- { hooks =
- defaultHooks
- { surfaceHook = do
- handleSurface
- },
- layout = WindowLayout Full,
- resetHook = do
- useInputHandler $
- withProxies inputProxies $ do
- ev <- nextInputEvent
+foreign export ccall "plugin_cold_start"
+ coldStart :: MontisColdStart
- bind ev (released btnLeft) $
- run $
- wio $
- putStrLn "Left Button Released!!"
+foreign export ccall "plugin_hot_start"
+ hotStart :: MontisHotStart
- unless (isPressEvent ev) $ do
- forwardEvent ev
- continue
+coldStart :: MontisColdStart
+coldStart = coldStartMontis config
- bind ev (Shift .+ Mod1 .+ 'R') $ run requestHotReload
+hotStart :: MontisHotStart
+hotStart = hotStartMontis config
- -- bind ev (Mod1 .+ 't') $ run (shellExec "alacritty")
-
- bind ev (Mod1 .+ 'p') $ do
- ev2 <- nextInputPressEvent
-
- bind ev2 (Mod1 .+ 'p') $
- run $
- wio $
- putStrLn "Test"
-
- bind ev (Mod1 .+ btnLeft) $
- run $
- wio $
- putStrLn "Left Button Press!!"
-
- bind ev (Mod1 .+ 'q') macroStartStopKeybind
-
- bind ev (weak $ Mod1 .+ '@') macroReplayKeybind
-
- bind ev (weak $ ModX 5 .+ btnLeft) $
- run $
- wio $
- putStrLn "Fake Modifier With Button!!!"
-
- bind ev (weak $ ModX 5 .+ 't') $
- run $
- wio $
- putStrLn "Fake Modifier!!"
-
- forwardEvent ev
- }
- where
- inputProxies ::
- Proxy
- '[ MacroSupport,
- MagicModifierProxy 59 SetXtra -- Only log keys when F1 (keycode 59 is pressed)
- ]
- inputProxies = Proxy
-
-data SetXtra
-
-instance InputProxy SetXtra where
- onKeyEvent _ ie =
- case ie of
- (InputKeyEvent ke@(KeyEvent {KeyEvent.modifiers = modifiers})) ->
- return $ InputKeyEvent ke {KeyEvent.modifiers = modifiers .|. modifierToMask (ModX 5)}
- (InputButtonEvent be@(ButtonEvent {ButtonEvent.modifiers = modifiers})) ->
- return $ InputButtonEvent be {ButtonEvent.modifiers = modifiers .|. modifierToMask (ModX 5)}
+config :: MontisConfig
+config = defaultConfig
diff --git a/plug/src/Lib.hs b/plug/src/Lib.hs
deleted file mode 100644
index d36ff27..0000000
--- a/plug/src/Lib.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Lib
- ( someFunc
- ) where
-
-someFunc :: IO ()
-someFunc = putStrLn "someFunc"
diff --git a/plug/src/Montis/Foreign/Import.hs b/plug/src/Montis/Base/Foreign/Runtime.hs
index e83841b..c43471e 100644
--- a/plug/src/Montis/Foreign/Import.hs
+++ b/plug/src/Montis/Base/Foreign/Runtime.hs
@@ -1,4 +1,4 @@
-module Montis.Foreign.Import where
+module Montis.Base.Foreign.Runtime where
import Data.Void
import Foreign.C (CInt (..), CString)
@@ -8,6 +8,6 @@ foreign import ccall "montis_do_request_hot_reload" foreign_doRequestHotReload :
foreign import ccall "montis_do_request_log" foreign_doRequestLog :: Ptr Void -> CString -> IO ()
-foreign import ccall "montis_do_request_exit" foregin_doExit :: Ptr Void -> CInt -> IO ()
+foreign import ccall "montis_do_request_exit" foreign_doRequestExit :: Ptr Void -> CInt -> IO ()
foreign import ccall "montis_plugin_get_seat" foreign_getSeat :: Ptr Void -> IO (Ptr Void)
diff --git a/plug/src/Montis/Base/Foreign/WlRoots.hs b/plug/src/Montis/Base/Foreign/WlRoots.hs
new file mode 100644
index 0000000..272567f
--- /dev/null
+++ b/plug/src/Montis/Base/Foreign/WlRoots.hs
@@ -0,0 +1,44 @@
+-- | Contains functions and thin wrappers via ffi to the wlroots API.
+module Montis.Base.Foreign.WlRoots where
+
+import Foreign (Ptr, Word32, nullPtr)
+import Montis.Base.Foreign.WlRoots.Types
+
+-- | Converts a null pointer into 'Nothing' to avoid dangling FFI handles.
+guardNull :: Ptr a -> Maybe (Ptr a)
+guardNull p | p == nullPtr = Nothing
+guardNull p = Just p
+
+foreign import ccall "wlr_seat_set_keyboard"
+ foreign_wlrSetSeatKeyboard ::
+ Ptr ForeignWlrSeat -> Ptr ForeignWlrInputDevice -> IO ()
+
+-- | Binds the given input device as the active keyboard for a seat.
+setSeatKeyboard :: WlrSeat -> WlrInputDevice -> IO ()
+setSeatKeyboard (WlrSeat p1) (WlrInputDevice p2) =
+ foreign_wlrSetSeatKeyboard p1 p2
+
+foreign import ccall "wlr_seat_get_keyboard"
+ foreign_wlrSeatGetKeyboard ::
+ Ptr ForeignWlrSeat -> IO (Ptr ForeignWlrKeyboard)
+
+-- | Looks up the current keyboard for a seat, if one exists.
+getSeatKeyboard :: WlrSeat -> IO (Maybe WlrKeyboard)
+getSeatKeyboard (WlrSeat p) =
+ fmap WlrKeyboard . guardNull <$> foreign_wlrSeatGetKeyboard p
+
+foreign import ccall "wlr_keyboard_get_modifiers"
+ foreign_wlrKeyboardGetModifiers ::
+ Ptr ForeignWlrKeyboard -> IO Word32
+
+-- | Returns the current keyboard modifier mask for the keyboard.
+getKeyboardModifiers :: WlrKeyboard -> IO Word32
+getKeyboardModifiers (WlrKeyboard p) = foreign_wlrKeyboardGetModifiers p
+
+foreign import ccall "wlr_seat_keyboard_notify_key"
+ foreign_wlrSeatKeyboardNotifyKey ::
+ Ptr ForeignWlrSeat -> Word32 -> Word32 -> Word32 -> IO ()
+
+-- | Forwards a key event to the seat with time, keycode, and state.
+seatKeyboardNotifyKey :: WlrSeat -> Word32 -> Word32 -> Word32 -> IO ()
+seatKeyboardNotifyKey (WlrSeat p) = foreign_wlrSeatKeyboardNotifyKey p
diff --git a/plug/src/Montis/Base/Foreign/WlRoots/Types.hs b/plug/src/Montis/Base/Foreign/WlRoots/Types.hs
new file mode 100644
index 0000000..51762d5
--- /dev/null
+++ b/plug/src/Montis/Base/Foreign/WlRoots/Types.hs
@@ -0,0 +1,85 @@
+module Montis.Base.Foreign.WlRoots.Types where
+
+import Foreign (IntPtr, Ptr, intPtrToPtr, ptrToIntPtr)
+import Text.Read
+
+-- | Opaque foreign type for a wlroots keyboard.
+data ForeignWlrKeyboard
+
+newtype WlrKeyboard where
+ WlrKeyboard :: Ptr ForeignWlrKeyboard -> WlrKeyboard
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for a wlroots pointer.
+data ForeignWlrPointer
+
+newtype WlrPointer where
+ WlrPointer :: Ptr ForeignWlrPointer -> WlrPointer
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for wlroots pointer button events.
+data ForeignWlrPointerButtonEvent
+
+newtype WlrPointerButtonEvent where
+ WlrPointerButtonEvent :: Ptr ForeignWlrPointerButtonEvent -> WlrPointerButtonEvent
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for a wlroots seat.
+data ForeignWlrSeat
+
+newtype WlrSeat where
+ WlrSeat :: Ptr ForeignWlrSeat -> WlrSeat
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for a wlroots input device.
+data ForeignWlrInputDevice
+
+newtype WlrInputDevice where
+ WlrInputDevice :: Ptr ForeignWlrInputDevice -> WlrInputDevice
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for wlroots keyboard key events.
+data ForeignWlrEventKeyboardKey
+
+newtype WlrEventKeyboardKey where
+ WlrEventKeyboardKey :: Ptr ForeignWlrEventKeyboardKey -> WlrEventKeyboardKey
+ deriving (Show, Ord, Eq)
+
+-- | Opaque foreign type for xdg-shell surfaces.
+data ForeignWlrXdgSurface
+
+-- | Opaque foreign type for XWayland surfaces.
+data ForeignWlrXWaylandSurface
+
+-- | Tagged wrapper over surface pointer variants.
+data Surface where
+ XdgSurface :: Ptr ForeignWlrXdgSurface -> Surface
+ XWaylandSurface :: Ptr ForeignWlrXWaylandSurface -> Surface
+ deriving (Ord, Eq)
+
+instance Show Surface where
+ show (XdgSurface p) = show (XdgSerializeSurface (ptrToIntPtr p))
+ show (XWaylandSurface p) = show (XWaylandSerializeSurface (ptrToIntPtr p))
+
+instance Read Surface where
+ readPrec = fmap toSurf readPrec
+ where
+ -- Rebuild constructors from serialized pointers.
+ toSurf (XdgSerializeSurface ip) = XdgSurface (intPtrToPtr ip)
+ toSurf (XWaylandSerializeSurface ip) = XWaylandSurface (intPtrToPtr ip)
+
+-- | Serializable version of 'Surface' to enable Read/Show.
+data SerializableSurface
+ = XdgSerializeSurface IntPtr
+ | XWaylandSerializeSurface IntPtr
+ deriving (Read, Show)
+
+class ForeignSurface a where
+ -- | Converts a foreign surface pointer into the tagged wrapper.
+ toSurface :: Ptr a -> Surface
+
+instance ForeignSurface ForeignWlrXdgSurface where
+ toSurface = XdgSurface
+
+instance ForeignSurface ForeignWlrXWaylandSurface where
+ toSurface = XWaylandSurface
diff --git a/plug/src/Montis/Constraints.hs b/plug/src/Montis/Constraints.hs
deleted file mode 100644
index 242f1fb..0000000
--- a/plug/src/Montis/Constraints.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
--- | Contains useful constraints and constraint combinators for type-level
--- metaprogramming.
-module Montis.Constraints where
-
--- | A null constraint. All types implement this.
-class Unconstrained a
-
-instance Unconstrained a
-
--- | Combines multiple constraints by 'And'ing them together.
-class (c1 a, c2 a) => (&&&&) c1 c2 a
-
-instance (c1 a, c2 a) => (&&&&) c1 c2 a
diff --git a/plug/src/Montis/Core.hs b/plug/src/Montis/Core.hs
index e01d2f7..65dcdad 100644
--- a/plug/src/Montis/Core.hs
+++ b/plug/src/Montis/Core.hs
@@ -1,371 +1,9 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Core where
-
-import Control.Arrow (Arrow (first))
-import Control.Monad ((<=<))
-import Control.Monad.RWS (MonadIO (liftIO), MonadReader (..), MonadState, modify)
-import Control.Monad.Reader (ReaderT (runReaderT))
-import Control.Monad.State (StateT (runStateT), gets, modify')
-import Control.Monad.Trans.Maybe
-import Data.Data (Typeable, cast, tyConModule, tyConName, tyConPackage)
-import Data.Default.Class (Default, def)
-import Data.Kind (Constraint, Type)
-import Data.Map (Map)
-import Data.Map qualified as M
-import Data.Maybe (fromMaybe, mapMaybe)
-import Data.Proxy
-import Data.Set (Set)
-import Data.Set qualified as Set
-import Data.Singletons.Decide (Void)
-import Foreign (Ptr, StablePtr, intPtrToPtr, ptrToIntPtr)
-import Montis.Core.ButtonEvent (ButtonEvent)
-import Montis.Core.KeyEvent
-import Montis.Core.SurfaceEvent
-import Montis.Foreign.ForeignInterface qualified as ForeignInterface
-import Montis.Foreign.WlRoots (Surface, WlrSeat)
-import Montis.StackSet hiding (layout)
-import Montis.StackSet qualified as StackSet
-import Text.Printf (printf)
-import Text.Read hiding (lift)
-import Type.Reflection (someTypeRep, someTypeRepTyCon)
-
-data RationalRect = RationalRect Rational Rational Rational Rational
-
--- | Wrapper for a message. Messages are sent to layout and layouts are supposed
--- to handle them. This hides a typeable parameter.
-data Message where
- Message :: (Typeable a) => a -> Message
-
--- | casts a message to a type.
-fromMessage :: (Typeable a) => Message -> Maybe a
-fromMessage (Message t) = cast t
-
--- | Wraps a type in a message.
-toMessage :: (Typeable a) => a -> Message
-toMessage = Message
-
-class (Typeable l) => HandleMessage l where
- handleMessage :: Message -> l -> MaybeT W l
- handleMessage _ = return
-
-newtype Window = Window
- { surface :: Surface
- }
- deriving (Show, Ord, Eq, Read)
-
--- | Types of this class "lay out" windows by assigning rectangles and handle
--- messages.
-class (Typeable l, HandleMessage l) => LayoutClass l where
- -- | Constraints on the type to lay out. Sometimes a layout requires the 'a'
- -- type to be "Ord", other times "Eq", this is the mechanism by which this
- -- constraint is expressed.
- type LayoutConstraint l :: Type -> Constraint
-
- -- | Runs the layout in an impure way returning a modified layout and the list
- -- of windows to their rectangles under a monad.
- runLayout :: (LayoutConstraint l a) => Stack a -> l -> W (l, [(a, RationalRect)])
-
- readLayout :: String -> Maybe l
- default readLayout :: (Read l) => String -> Maybe l
- readLayout = readMaybe
-
- serializeLayout :: l -> String
- default serializeLayout :: (Show l) => l -> String
- serializeLayout = show
-
- description :: l -> String
- default description :: (Show l) => l -> String
- description = show
- {-# MINIMAL runLayout #-}
-
--- | Lifts a pure-layout implementation to a signature that complies with
--- 'runLayout'
-pureLayout ::
- (Stack a -> l -> [(a, RationalRect)]) ->
- Stack a ->
- l ->
- W (l, [(a, RationalRect)])
-pureLayout fn as l = return (l, fn as l)
-
--- A Layout which hides the layout parameter under an existential type and
--- asserts the layout hidden can work with Window types.
-data WindowLayout
- = forall l a.
- (LayoutClass l, LayoutConstraint l a, a ~ Window) =>
- WindowLayout l
-
-runWindowLayout :: Stack Window -> WindowLayout -> W (WindowLayout, [(Window, RationalRect)])
-runWindowLayout as (WindowLayout l) = first WindowLayout <$> runLayout as l
-
-handleWindowMessage :: Message -> WindowLayout -> MaybeT W WindowLayout
-handleWindowMessage m (WindowLayout l) = WindowLayout <$> handleMessage m l
-
--- | Using the 'Layout' as a witness, parse existentially wrapped windows
--- from a 'String'.
-readWindowLayout :: WindowLayout -> String -> WindowLayout
-readWindowLayout (WindowLayout l) s
- | (Just x) <- readLayout s =
- WindowLayout (asTypeOf x l)
-readWindowLayout l _ = l
-
--- | Serializes a window layout to a string.
-serializeWindowLayout :: WindowLayout -> String
-serializeWindowLayout (WindowLayout l) = serializeLayout l
-
-type ScreenId = ()
-
-type ScreenDetail = ()
-
-type Tag = String
-
-newtype ReadPtr a = ReadPtr (Ptr ())
-
-instance Read (ReadPtr a) where
- readPrec = fmap (ReadPtr . intPtrToPtr) readPrec
-
-instance Show (ReadPtr a) where
- show (ReadPtr ptr) = show (ptrToIntPtr ptr)
-
-type Montis = StablePtr (Context, State)
-
-data Plugin
-
--- Read-only context under which montis is run under.
-data Context = Context
- { ctxConfig :: Config WindowLayout,
- ctxPluginPtr :: Ptr Plugin
- }
-
-defaultHooks :: Hooks
-defaultHooks =
- Hooks
- { keyHook = \_ -> return (),
- surfaceHook = handleSurface,
- buttonHook = \_ -> return ()
- }
-
-defaultConfig :: Config ()
-defaultConfig =
- Config
- { hooks = defaultHooks,
- layout = (),
- resetHook = return ()
- }
-
-data Hooks = Hooks
- { keyHook :: KeyEvent -> W (),
- surfaceHook :: SurfaceEvent -> W (),
- buttonHook :: ButtonEvent -> W ()
- }
-
-data Config l = Config
- { layout :: l,
- hooks :: Hooks,
- resetHook :: W ()
- }
-
--- | Typeclass defining the set of types which can be used as state extensions
--- to the W monad. These state extensions may be persistent or not.
---
--- There are default implementations for all methods if the type implements
--- Read, Show and Default,
-class (Typeable a) => ExtensionClass a where
- -- | The initial value used for the first time an extension is 'gotten' or
- -- demarshalling fails.
- initialValue :: a
-
- -- | Transforms a type into a string. If the type cannot be marshalled, this
- -- function should return Nothing.
- --
- -- If a type cannot be marshalled, it cannot persist across hot reloads.
- marshalExtension :: a -> Maybe String
-
- -- | Reads an extension from a string. If this type is not marshallable or
- -- reading fails, this function should return Nothing.
- demarshalExtension :: String -> Maybe a
-
- -- | If the type implements Default, use the default implementation.
- default initialValue :: (Default a) => a
- initialValue = def
-
- -- | If the type implements Show, use show for the marshalling.
- default marshalExtension :: (Show a) => a -> Maybe String
- marshalExtension = Just . show
-
- -- | If the type implements Read, use read for the demarshalling.
- default demarshalExtension :: (Read a) => String -> Maybe a
- demarshalExtension = readMaybe
-
-data StateExtension where
- StateExtension :: (ExtensionClass a) => a -> StateExtension
-
--- | Puts a state extension.
-xput :: forall a m. (ExtensionClass a, Wlike m) => a -> m ()
-xput val = liftW $ do
- modify' $ \state@State {extensibleState = extensibleState} ->
- state
- { extensibleState =
- M.insert
- ( xRepr (Proxy :: Proxy a)
- )
- (Right $ StateExtension val)
- extensibleState
- }
-
--- | Modifies a state extension.
-xmodify :: forall a m. (ExtensionClass a, Wlike m) => (a -> a) -> m ()
-xmodify fn = xput . fn =<< xget
-
--- | Modifies a state extension in the monadic context.
-xmodifyM :: forall a m. (ExtensionClass a, Wlike m) => (a -> m a) -> m ()
-xmodifyM fn = (xput <=< fn) =<< xget
-
--- | Produces a string representation of a type used to key into the extensible
--- state map.
-xRepr :: forall proxy a. (ExtensionClass a) => proxy a -> String
-xRepr _ = tyconToStr $ someTypeRepTyCon (someTypeRep (Proxy :: Proxy a))
- where
- tyconToStr tc =
- printf "%s.%s.%s" (tyConPackage tc) (tyConModule tc) (tyConName tc)
-
--- | Gets a state extension.
-xget :: forall a m. (ExtensionClass a, Wlike m) => m a
-xget = do
- xs <- liftW $ gets extensibleState
- case M.lookup (xRepr (Proxy :: Proxy a)) xs of
- Just (Right (StateExtension a)) -> return (fromMaybe initialValue (cast a))
- Just (Left str) ->
- let v = fromMaybe initialValue (demarshalExtension str)
- in xput v >> return v
- Nothing ->
- xput (initialValue :: a) >> return initialValue
-
-xgets :: forall a b m. (ExtensionClass a, Wlike m) => (a -> b) -> m b
-xgets fn = fn <$> xget
-
--- State as it is marshalled. Used for derived instances of Show and Read.
-data MarshalledState
- = MarshalledState
- (StackSet ScreenId ScreenDetail Tag String Window)
- (Set Window)
- [(String, String)]
- deriving (Show, Read)
-
-data State = State
- { -- The datastructure containing the state of the windows.
- mapped :: StackSet ScreenId ScreenDetail Tag WindowLayout Window,
- -- | All the windows wetterhorn knows about, even if they are not mapped.
- allWindows :: Set Window,
- -- | Current set of hooks. The initial hooks are provided by the
- -- configuration, but the hooks can change during operation. This is how key
- -- sequences can be mapped.
- currentHooks :: Hooks,
- -- | Map from the typerep string to the state extension.
- extensibleState :: Map String (Either String StateExtension)
- }
-
--- | Initializes a "cold" state from a configuration. A cold state is the
--- initial state on startup. It is constrasted with a "hot" state, which is a
--- persisted state after a hot-reload.
-initColdState :: Config WindowLayout -> IO State
-initColdState Config {layout = layout, hooks = hooks} =
- return $
- State
- ( StackSet (Screen () () (Workspace "0" layout (Stack [] []))) [] []
- )
- mempty
- hooks
- mempty
-
--- | Marshals the serializable parts of the state to a string. This happens
--- during a hot-reload where some state must be saved to persist across hot
--- reloads.
-marshalState :: State -> String
-marshalState
- ( State
- { mapped = mapped,
- allWindows = allWindows,
- extensibleState = xs
- }
- ) =
- show $
- MarshalledState
- (mapLayout serializeWindowLayout mapped)
- allWindows
- (mapMaybe (\(k, v) -> (k,) <$> doMarshalEx v) (M.toList xs))
- where
- doMarshalEx (Left s) = Just s
- doMarshalEx (Right (StateExtension a)) = marshalExtension a
-
--- | Demarshals the string from "marshalState" into a state. Uses the provided
--- config to fill out non-persistent parts of the state.
-demarshalState :: Config WindowLayout -> String -> State
-demarshalState Config {hooks = hooks, layout = layout} str =
- State mapped allWindows hooks xs
- where
- ( MarshalledState
- (mapLayout (readWindowLayout layout) -> mapped)
- allWindows
- (fmap Left . M.fromList -> xs)
- ) = read str
-
--- | This is _the_ main monad used for Montis operations. Contains
--- everything required to operate. Contains the state, configuration and
--- interface to foreign code.
-newtype W a = W (ReaderT Context (StateT State IO) a)
- deriving (Functor, Applicative, Monad, MonadState State, MonadIO)
-
--- | Let Config be the thing W is a reader for. There is already a way to get
--- the foreign interface in the context.
-instance MonadReader (Config WindowLayout) W where
- local fn (W r) =
- W $
- local
- ( \(Context conf pluginPtr) -> Context (fn conf) pluginPtr
- )
- r
- ask = W $ ctxConfig <$> ask
-
-runW :: W a -> (Context, State) -> IO (a, State)
-runW (W fn) (ctx, st) = runStateT (runReaderT fn ctx) st
-
-wio :: IO a -> W a
-wio = liftIO
-
--- | Type class to lift an arbitrary 'W' computation into another monad.
-class (Monad m) => Wlike m where
- liftW :: W a -> m a
-
--- | Trivial instance of W for Wlike.
-instance Wlike W where
- liftW = id
-
--- Default implementations for common handlers.
-
--- | handles a new surface event. This updates the state to reflect how it
--- should look in the harness.
-handleSurface :: SurfaceEvent -> W ()
-handleSurface (SurfaceEvent state (Window -> win)) =
- case state of
- Destroy ->
- modify $
- \st@State
- { allWindows = allWindows,
- mapped = mapped
- } ->
- st
- { allWindows = Set.delete win allWindows,
- mapped = StackSet.delete win mapped
- }
- Unmap -> modify $
- \st@State {mapped = mapped} ->
- st
- { mapped = StackSet.delete win mapped
- }
- Map -> modify $
- \st@State {mapped = mapped, allWindows = allWindows} ->
- st
- { mapped = StackSet.insertTiled win mapped,
- allWindows = Set.insert win allWindows
- }
+module Montis.Core
+ ( module X,
+ )
+where
+
+import Montis.Core.Events as X
+import Montis.Core.Monad as X
+import Montis.Core.Start as X
+import Montis.Core.State as X
diff --git a/plug/src/Montis/Core/ButtonEvent.hs b/plug/src/Montis/Core/ButtonEvent.hs
deleted file mode 100644
index 3a79922..0000000
--- a/plug/src/Montis/Core/ButtonEvent.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Core.ButtonEvent where
-
-import Data.Word (Word32)
-import Foreign (Ptr)
-import Montis.Foreign.WlRoots
-
-data ButtonState = ButtonReleased | ButtonPressed deriving (Show, Read, Eq, Enum, Ord)
-
-data ButtonEvent = ButtonEvent
- { pointer :: Ptr WlrPointer,
- timeMs :: Word32,
- button :: Word32,
- modifiers :: Word32,
- state :: ButtonState
- }
- deriving (Eq, Show, Ord)
diff --git a/plug/src/Montis/Core/Events.hs b/plug/src/Montis/Core/Events.hs
new file mode 100644
index 0000000..8c0742e
--- /dev/null
+++ b/plug/src/Montis/Core/Events.hs
@@ -0,0 +1,37 @@
+module Montis.Core.Events where
+
+import Data.Word (Word32)
+import Montis.Base.Foreign.WlRoots.Types
+
+data KeyState = KeyPressed | KeyReleased deriving (Show, Read, Eq, Enum, Ord)
+
+data KeyEvent = KeyEvent
+ { keyEvent_timeMs :: Word32,
+ keyEvent_keycode :: Word32,
+ keyEvent_state :: KeyState,
+ keyEvent_modifiers :: Word32,
+ keyEvent_keysym :: Word32,
+ keyEvent_codepoint :: Char,
+ keyEvent_device :: WlrInputDevice
+ }
+ deriving (Show, Ord, Eq)
+
+data ButtonState = ButtonReleased | ButtonPressed deriving (Show, Read, Eq, Enum, Ord)
+
+data ButtonEvent = ButtonEvent
+ { buttonEvent_pointer :: WlrPointer,
+ buttonEvent_timeMs :: Word32,
+ buttonEvent_button :: Word32,
+ buttonEvent_modifiers :: Word32,
+ buttonEvent_state :: ButtonState
+ }
+ deriving (Eq, Show, Ord)
+
+data SurfaceState = Map | Unmap | Destroy
+ deriving (Eq, Ord, Show, Read, Enum)
+
+data SurfaceEvent = SurfaceEvent
+ { surfaceEvent_state :: SurfaceState,
+ surfaceEvent_surface :: Surface
+ }
+ deriving (Eq, Ord, Show)
diff --git a/plug/src/Montis/Core/Extensions.hs b/plug/src/Montis/Core/Extensions.hs
new file mode 100644
index 0000000..a44debe
--- /dev/null
+++ b/plug/src/Montis/Core/Extensions.hs
@@ -0,0 +1,29 @@
+module Montis.Core.Extensions where
+
+import Data.Data
+ ( Typeable,
+ tyConModule,
+ tyConName,
+ tyConPackage,
+ )
+import Data.Kind (Constraint, Type)
+import Text.Printf (printf)
+import Type.Reflection (someTypeRep, someTypeRepTyCon)
+
+-- | A key to key into the Extension maps.
+data ExtensionKey where
+ ExtensionKey :: {extensionKeyValue :: String} -> ExtensionKey
+ deriving (Eq, Ord, Show)
+
+data Extension (c :: Type -> Constraint) where
+ Extension :: (Typeable a, c a) => a -> Extension c
+
+class Nil a
+
+-- | Produces a string representation of a type used to key into the extensible
+-- state map.
+typeRepr :: forall proxy a. (Typeable a) => proxy a -> ExtensionKey
+typeRepr proxy = ExtensionKey $ tyconToStr $ someTypeRepTyCon (someTypeRep proxy)
+ where
+ tyconToStr tc =
+ printf "%s.%s.%s" (tyConPackage tc) (tyConModule tc) (tyConName tc)
diff --git a/plug/src/Montis/Core/Internal/Foreign/Export.hs b/plug/src/Montis/Core/Internal/Foreign/Export.hs
new file mode 100644
index 0000000..fc535c9
--- /dev/null
+++ b/plug/src/Montis/Core/Internal/Foreign/Export.hs
@@ -0,0 +1,85 @@
+-- | This module has no public functions, but provides the surface interface
+-- between the Montis runtime and the plugin.
+module Montis.Core.Internal.Foreign.Export () where
+
+import Control.Monad (forM_)
+import Data.ByteString qualified as BS
+import Data.ByteString.Char8 qualified as CH
+import Data.Singletons.Decide (Void)
+import Foreign
+ ( Ptr,
+ Storable (poke, pokeByteOff),
+ Word32,
+ Word8,
+ deRefStablePtr,
+ freeStablePtr,
+ mallocBytes,
+ newStablePtr,
+ )
+import Foreign.C (CChar, CInt (..))
+import Montis.Base.Foreign.WlRoots.Types (ForeignWlrXdgSurface, WlrEventKeyboardKey, WlrInputDevice, WlrPointerButtonEvent)
+import Montis.Core
+import Montis.Core.State.Marshal (marshalState)
+import Montis.Core.State
+
+-- | Marshals the opaque state to a C-style byte array and size pointer.
+foreign export ccall "plugin_marshal_state"
+ pluginMarshalState :: OpqStT -> Ptr Word32 -> IO (Ptr Word8)
+
+pluginMarshalState :: OpqStT -> Ptr Word32 -> IO (Ptr Word8)
+pluginMarshalState opqStT outlen = do
+ (_, st) <- deRefStablePtr opqStT
+ let bs = CH.pack (marshalState st)
+ ret <- mallocBytes (BS.length bs)
+ poke outlen (fromIntegral $ BS.length bs)
+ forM_ (zip [0 ..] (BS.unpack bs)) $ \(off, w8) -> do
+ pokeByteOff ret off w8
+ return ret
+
+foreign export ccall "plugin_handle_button"
+ pluginHandleButton :: Ptr WlrPointerButtonEvent -> Word32 -> OpqStT -> IO OpqStT
+
+pluginHandleButton :: Ptr WlrPointerButtonEvent -> Word32 -> OpqStT -> IO OpqStT
+pluginHandleButton = undefined
+
+foreign export ccall "plugin_handle_keybinding"
+ pluginHandleKeybinding ::
+ Ptr WlrInputDevice ->
+ Ptr WlrEventKeyboardKey ->
+ Word32 ->
+ Word32 ->
+ Word32 ->
+ Ptr CInt ->
+ OpqStT ->
+ IO OpqStT
+
+pluginHandleKeybinding ::
+ Ptr WlrInputDevice ->
+ Ptr WlrEventKeyboardKey ->
+ Word32 ->
+ Word32 ->
+ Word32 ->
+ Ptr CInt ->
+ OpqStT ->
+ IO OpqStT
+pluginHandleKeybinding = undefined
+
+-- | Function exported to the harness to handle the mapping/unmapping/deletion
+-- of an XDG surface.
+foreign export ccall "plugin_handle_surface"
+ pluginHandleSurface ::
+ Ptr Void -> CInt -> OpqStT -> IO OpqStT
+
+pluginHandleSurface ::
+ Ptr Void -> CInt -> OpqStT -> IO OpqStT
+pluginHandleSurface = undefined
+
+-- | Function exported to the harness to handle the mapping/unmapping/deletion
+-- of an XWayland surface.
+foreign export ccall "plugin_handle_xwayland_surface"
+ pluginHandleXWaylandSurface ::
+ Ptr Void -> CInt -> OpqStT -> IO OpqStT
+
+pluginHandleXWaylandSurface ::
+ Ptr Void -> CInt -> OpqStT -> IO OpqStT
+pluginHandleXWaylandSurface = undefined
diff --git a/plug/src/Montis/Core/KeyEvent.hs b/plug/src/Montis/Core/KeyEvent.hs
deleted file mode 100644
index cbdda4f..0000000
--- a/plug/src/Montis/Core/KeyEvent.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Montis.Core.KeyEvent
- ( KeyEvent (..),
- KeyState (..),
- )
-where
-
-import Data.Word (Word32)
-import Foreign (Ptr)
-import Montis.Foreign.WlRoots
-
-data KeyState = KeyPressed | KeyReleased deriving (Show, Read, Eq, Enum, Ord)
-
-data KeyEvent = KeyEvent
- { timeMs :: Word32,
- keycode :: Word32,
- state :: KeyState,
- modifiers :: Word32,
- keysym :: Word32,
- codepoint :: Char,
- device :: Ptr WlrInputDevice
- }
- deriving (Show, Ord, Eq)
diff --git a/plug/src/Montis/Core/Keys.hs b/plug/src/Montis/Core/Keys.hs
deleted file mode 100644
index fb55cc6..0000000
--- a/plug/src/Montis/Core/Keys.hs
+++ /dev/null
@@ -1,240 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Core.Keys where
-
-import Control.Monad (forever, void, when)
-import Control.Monad.Cont.Class
-import Control.Monad.IO.Class
-import Control.Monad.State (MonadState (get, put), MonadTrans (lift), StateT, evalStateT, gets, modify)
-import Control.Monad.Trans.Cont
-import Data.Bits
-import Data.Word
-import Montis.Core.ButtonEvent (ButtonEvent)
-import qualified Montis.Core.ButtonEvent as ButtonEvent
-import Montis.Core.KeyEvent
-import qualified Montis.Core.KeyEvent as KeyEvent
-import Montis.Core.W
-import Montis.Foreign.WlRoots (wlrSeatKeyboardNotifyKey, wlrSeatSetKeyboard)
-import Montis.Core.Runtime (getSeat)
-
--- | Forwards the given key event to the focused window.
-forwardKey :: KeyEvent -> W ()
-forwardKey keyEvent = do
- seatPtr <- getSeat
- wio $ do
- wlrSeatSetKeyboard
- seatPtr
- (device keyEvent)
-
- wlrSeatKeyboardNotifyKey
- seatPtr
- (timeMs keyEvent)
- (keycode keyEvent)
- ( case state keyEvent of
- KeyReleased -> 0
- _ -> 1
- )
-
--- | Forwards the current key event to the focused window.
-forwardEvent :: KeyEvent -> KeysM ()
-forwardEvent = liftW . forwardKey
-
--- | Enumeration of possible modifiers
-data Modifier = Shift | Lock | Control | Mod1 | Mod2 | Mod3 | Mod4 | Mod5
- deriving (Eq, Ord, Show, Read, Enum, Bounded)
-
--- | Converts a modifier to its associated mask.
-modifierToMask :: Modifier -> Word32
-modifierToMask m =
- 1
- `shiftL` case m of
- Shift -> 0
- Lock -> 1
- Control -> 2
- Mod1 -> 3
- Mod2 -> 4
- Mod3 -> 5
- Mod4 -> 6
- Mod5 -> 7
-
-data KeysState = KeysState
- { -- | Reference to the top. Used for a continue statement.
- keysTop :: KeysM (),
- handleContinuation :: KeyContinuation -> W ()
- }
-
--- | The Keys monad. This monad abstracts away control flow for handling key
--- bindings. This makes it easy to make key-sequence bindings.
--- newtype KeysM a = KeysM ((KeyEvent -> W ()) -> KeyEvent -> W (KeysMR a))
-newtype KeysM a = KeysM (ContT () (StateT KeysState W) a)
- deriving (Monad, Functor, Applicative, MonadCont, MonadIO)
-
--- | KeysM can be lifted from a W action.
-instance Wlike KeysM where
- liftW = KeysM . lift . lift
-
-type KeyContinuation = KeyEvent -> W ()
-
-useKeysWithContinuation :: (KeyContinuation -> W ()) -> KeysM () -> W ()
-useKeysWithContinuation continuation (forever -> km@(KeysM c)) =
- evalStateT (evalContT c) (KeysState km continuation)
-
-useKeys :: KeysM () -> W ()
-useKeys = useKeysWithContinuation putKeyHandler
-
--- | Returns the next key event.
-nextKeyEvent :: KeysM KeyEvent
-nextKeyEvent = do
- st <- KeysM $ lift get
- KeysM $
- shiftT
- ( \keyHandler ->
- lift . lift $
- handleContinuation st (\kp -> evalStateT (keyHandler kp) st)
- )
-
--- | Discards the rest of the continuation and starts again from the top. Useful
--- for keybinds where once the key is handled, there's nothing left to do.
-continue :: KeysM ()
-continue = do
- st <- KeysM $ lift get
- let (KeysM topCont) = keysTop st
-
- -- This shift discards the rest of the computation and instead returns to the
- -- top of the handler.
- KeysM $ shiftT (\_ -> resetT topCont)
-
--- | Returns the "top" continuation.
-getTop :: KeysM (KeysM ())
-getTop = KeysM (gets keysTop)
-
-putKeyHandler :: KeyContinuation -> W ()
-putKeyHandler handler = do
- s@State {currentHooks = hooks} <- get
- put
- s
- { currentHooks =
- hooks
- { keyHook = void <$> handler
- }
- }
-
-nextButtonEvent :: KeysM ButtonEvent
-nextButtonEvent = do
- st <- KeysM get
- KeysM $
- shiftT $ \h ->
- lift $ lift $ putButtonHandler (\ev -> evalStateT (h ev) st)
- where
- putButtonHandler h = do
- modify $ \st -> st {currentHooks = (currentHooks st) {buttonHook = h}}
-
-nextButtonOrKeyEvent :: KeysM (Either ButtonEvent KeyEvent)
-nextButtonOrKeyEvent = do
- st <- KeysM get
- KeysM $
- shiftT $ \rest ->
- lift $
- lift $ do
- putButtonHandler (\ev -> evalStateT (rest (Left ev)) st)
- handleContinuation st (\ev -> evalStateT (rest (Right ev)) st)
- where
- putButtonHandler h = do
- modify $ \st -> st {currentHooks = (currentHooks st) {buttonHook = h}}
-
-nextButtonOrKeyPress :: KeysM (Either ButtonEvent KeyEvent)
-nextButtonOrKeyPress = do
- ev <- nextButtonOrKeyEvent
- case ev of
- Left bev | ButtonEvent.state bev == ButtonEvent.ButtonPressed -> return ev
- Left bev -> forwardButtonEvent bev >> nextButtonOrKeyPress
- Right kev | KeyEvent.state kev == KeyEvent.KeyPressed -> return ev
- Right kev -> forwardEvent kev >> nextButtonOrKeyPress
- where
- forwardButtonEvent _ = return ()
-
--- | Returns the next KeyPressed event. This is likely what 90% of use cases
--- want rather than nextKeyEvent.
-nextKeyPress :: KeysM KeyEvent
-nextKeyPress = do
- k <- nextKeyEvent
- if KeyEvent.state k /= KeyPressed
- then forwardEvent k >> nextKeyPress
- else return k
-
---
--- binding EDSL used to expressively create key bindings and subbindings inside
--- a KeysM () context.
---
-
-data KeyMatcher = KeyMatcher Word32 Char
- deriving (Show)
-
--- | Like a KeyMatcher, but allows additional modifiers to be pressed, not just
--- the exact ones given.
-newtype WeakKeyMatcher = WeakKeyMatcher KeyMatcher
-
--- | Converts a KeyMatcher to a weak key matcher.
-weak :: KeyMatcher -> WeakKeyMatcher
-weak = WeakKeyMatcher
-
-class KeyMatcherId r where
- toKeyMatcher :: r -> KeyMatcher
-
-instance KeyMatcherId KeyMatcher where
- toKeyMatcher = id
-
-instance KeyMatcherId Char where
- toKeyMatcher = KeyMatcher 0
-
-class KeyMatcherBuilder b where
- (.+) :: (KeyMatcherId i) => b -> i -> KeyMatcher
-
-instance KeyMatcherBuilder Modifier where
- (.+) m (toKeyMatcher -> (KeyMatcher mods ch)) =
- KeyMatcher (mods .|. modifierToMask m) ch
-
-infixr 9 .+
-
-class MatchKey m where
- matchKey :: m -> KeyEvent -> Bool
-
-instance MatchKey (KeyEvent -> Bool) where
- matchKey = ($)
-
-instance MatchKey Bool where
- matchKey = const
-
-instance MatchKey Char where
- matchKey ch ev = ch == KeyEvent.codepoint ev
-
-instance MatchKey KeyMatcher where
- matchKey (KeyMatcher m ch) ev =
- ch == KeyEvent.codepoint ev && m == KeyEvent.modifiers ev
-
-instance MatchKey WeakKeyMatcher where
- matchKey (WeakKeyMatcher (KeyMatcher m ch)) ev =
- ch == KeyEvent.codepoint ev && (m .|. ms) == ms
- where
- ms = KeyEvent.modifiers ev
-
-class IsKeysM m where
- toKeysM :: m a -> KeysM a
-
-instance IsKeysM W where
- toKeysM = liftW
-
-instance IsKeysM KeysM where
- toKeysM = id
-
-bind :: (MatchKey m, IsKeysM k) => KeyEvent -> m -> k () -> KeysM ()
-bind ev m act = do
- when (matchKey m ev) $ do
- toKeysM act
- continue
-
-ignoreReleaseEvents :: KeyEvent -> KeysM ()
-ignoreReleaseEvents ev = do
- when (KeyEvent.state ev /= KeyEvent.KeyPressed) $ do
- forwardEvent ev
- continue
diff --git a/plug/src/Montis/Core/Monad.hs b/plug/src/Montis/Core/Monad.hs
new file mode 100644
index 0000000..06cc0db
--- /dev/null
+++ b/plug/src/Montis/Core/Monad.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Montis.Core.Monad where
+
+import Control.Monad.Identity (Identity (Identity))
+import Control.Monad.Reader
+import Control.Monad.State (MonadState, StateT (runStateT), gets, modify')
+import Data.Map qualified as Map
+import Data.Typeable
+import Foreign (StablePtr)
+import Montis.Core.Extensions (Extension (Extension), typeRepr)
+import Montis.Core.State
+
+-- | A Config type for the Montis monad.
+type MontisConfig = Config Montis
+
+-- | A Context type specific for the Montis monad.
+type MontisContext = Context Montis
+
+-- | A State type for the Montis monad.
+type MontisState = State Montis
+
+-- | The Opaque State Type passed between the plugin and the runtime. The
+-- OpqStT *is* the opq_st_t from the runtime code.
+type OpqStT = StablePtr (MontisContext, MontisState)
+
+-- | THe Montis monad.
+newtype Montis a where
+ Montis :: (ReaderT MontisContext (StateT MontisState IO) a) -> Montis a
+ deriving (Functor, Applicative, Monad, MonadState MontisState, MonadIO)
+
+-- | Monad reader instance for Montis.
+instance MonadReader MontisConfig Montis where
+ ask :: Montis MontisConfig
+ ask = Montis $ asks ctxConfig
+
+ reader :: (MontisConfig -> a) -> Montis a
+ reader fn = Montis $ asks (fn . ctxConfig)
+
+ local :: (MontisConfig -> MontisConfig) -> Montis a -> Montis a
+ local cfn (Montis fn) =
+ Montis $ local (\ctx -> ctx {ctxConfig = cfn (ctxConfig ctx)}) fn
+
+runMontis :: MontisContext -> MontisState -> Montis a -> IO (a, MontisState)
+runMontis ctx initState (Montis m) = runStateT (runReaderT m ctx) initState
+
+-- | The standard default config.
+defaultConfig :: MontisConfig
+defaultConfig =
+ Config
+ { startingHooks =
+ Hooks
+ { keyHook = liftIO . print,
+ surfaceHook = liftIO . print,
+ buttonHook = liftIO . print
+ },
+ startupHook = return (),
+ resetHook = return (),
+ configExtensions = mempty
+ }
+
+xStatePut :: forall a. (StateExtension a) => a -> Montis ()
+xStatePut xst = do
+ modify'
+ ( \st ->
+ st
+ { extensibleState =
+ Map.insert
+ (typeRepr (Identity xst))
+ (Right (Extension xst))
+ (extensibleState st)
+ }
+ )
+
+xStateGet :: forall a. (StateExtension a) => Montis (Maybe a)
+xStateGet = do
+ mp <- gets extensibleState
+ case lookupByType (Proxy :: Proxy a) mp of
+ Nothing -> return Nothing
+ Just (Right (Extension v)) -> return (cast v)
+ Just (Left s) -> do
+ let x = (demarshalExtension s :: Maybe a)
+ in forM_ x xStatePut >> return x
diff --git a/plug/src/Montis/Core/Plugin/Interface.hs b/plug/src/Montis/Core/Plugin/Interface.hs
new file mode 100644
index 0000000..73c0371
--- /dev/null
+++ b/plug/src/Montis/Core/Plugin/Interface.hs
@@ -0,0 +1,20 @@
+-- | Provides the plugin interface through foreign exports.
+module Montis.Core.Plugin.Interface where
+
+import Data.ByteString (ByteString)
+import Data.Data (Typeable)
+import Data.Singletons.Decide (Void)
+import Foreign (Ptr, StablePtr, Word32)
+import Foreign.C (CChar)
+import Montis.Core.State (SelfPtr)
+
+-- type OpqStT l w = StablePtr (Context l w, State l w)
+
+class OpaqueState s where
+ hotStart :: SelfPtr -> ByteString -> IO s
+
+ coldStart :: SelfPtr -> IO s
+
+ marshalState :: s -> ByteString
+
+ teardown :: s -> IO ()
diff --git a/plug/src/Montis/Core/Runtime.hs b/plug/src/Montis/Core/Runtime.hs
deleted file mode 100644
index 6521fba..0000000
--- a/plug/src/Montis/Core/Runtime.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Montis.Core.Runtime where
-
-import Control.Monad.RWS (MonadReader (ask))
-import Data.Void (Void)
-import Foreign (Ptr, castPtr)
-import Montis.Core.W
-import Montis.Foreign.Import
-import Montis.Foreign.WlRoots (WlrSeat)
-
-requestHotReload :: W ()
-requestHotReload = do
- Context {ctxPlugin = plug} <- W ask
- wio $ foreign_doRequestHotReload plug
-
-requestLog :: String -> W ()
-requestLog = undefined
-
-requestExit :: Int -> W ()
-requestExit = undefined
-
-newtype Seat = Seat (Ptr Void)
-
-getSeat :: W (Ptr WlrSeat)
-getSeat = do
- Context {ctxPlugin = plug} <- W ask
- wio $ castPtr <$> foreign_getSeat plug
diff --git a/plug/src/Montis/Core/Start.hs b/plug/src/Montis/Core/Start.hs
new file mode 100644
index 0000000..54ec8c5
--- /dev/null
+++ b/plug/src/Montis/Core/Start.hs
@@ -0,0 +1,38 @@
+module Montis.Core.Start where
+
+import Data.ByteString qualified as BS
+import Data.ByteString.Char8 qualified as CH
+import Data.Void
+import Foreign (Word32, newStablePtr)
+import Foreign.C (CChar)
+import Foreign.Ptr
+import Montis.Core.Monad
+import Montis.Core.State
+import Montis.Core.State.Marshal (demarshalState)
+
+type MontisColdStart = Ptr Void -> IO OpqStT
+
+type MontisHotStart = Ptr Void -> Ptr CChar -> Word32 -> IO OpqStT
+
+hotStartMontis :: MontisConfig -> MontisHotStart
+hotStartMontis config self chars len = do
+ bs <- BS.packCStringLen (chars, fromIntegral len)
+
+ let ctx = Context config (SelfPtr self)
+ st = demarshalState config (CH.unpack bs)
+
+ ((), st') <- runMontis ctx st (resetHook config)
+ newStablePtr (ctx, st')
+
+-- Used to start montis given the provided config.
+coldStartMontis :: MontisConfig -> MontisColdStart
+coldStartMontis conf selfPtr =
+ let ctx = Context conf (SelfPtr selfPtr)
+ st =
+ State
+ { currentHooks = startingHooks conf,
+ extensibleState = mempty
+ }
+ in do
+ ((), st') <- runMontis ctx st (startupHook conf)
+ newStablePtr (ctx, st')
diff --git a/plug/src/Montis/Core/State.hs b/plug/src/Montis/Core/State.hs
new file mode 100644
index 0000000..303830b
--- /dev/null
+++ b/plug/src/Montis/Core/State.hs
@@ -0,0 +1,99 @@
+-- | Definitions of montis core state.
+module Montis.Core.State where
+
+import Data.Data (Typeable)
+import Data.Default.Class (Default, def)
+import Data.Map qualified as M
+import Data.Void (Void)
+import Foreign (Ptr)
+import Montis.Core.Events
+import Montis.Core.Extensions
+import Text.Read (readMaybe)
+
+-- | An opaque type used for the plugin's self-reference.
+newtype SelfPtr where
+ SelfPtr :: Ptr Void -> SelfPtr
+
+-- | This is the context the plugin operates under. The context contains data
+-- which must be provided by the runtime or the configuration. This data may not
+-- be cold-created.
+--
+-- Parameters:
+-- `m` the monad for this Context. This is typically W.
+data Context m where
+ Context ::
+ { ctxConfig :: Config m,
+ ctxSelfPtr :: SelfPtr
+ } ->
+ Context m
+
+-- | Montis configuration. This is the structure that defines the user-written
+-- configuration, which is linked in.
+data Config m where
+ Config ::
+ { startingHooks :: Hooks m,
+ startupHook :: m (),
+ resetHook :: m (),
+ configExtensions :: M.Map ExtensionKey (Extension Nil)
+ } ->
+ Config m
+
+-- | Hooks the runtime can call.
+data Hooks m where
+ Hooks ::
+ { keyHook :: KeyEvent -> m (),
+ surfaceHook :: SurfaceEvent -> m (),
+ buttonHook :: ButtonEvent -> m ()
+ } ->
+ Hooks m
+
+-- | Typeclass defining the set of types which can be used as state extensions
+-- to the W monad. These state extensions may be persistent or not.
+--
+-- There are default implementations for all methods if the type implements
+-- Read, Show and Default,
+class (Typeable a) => StateExtension a where
+ -- | The initial value used for the first time an extension is 'gotten' or
+ -- demarshalling fails.
+ initialValue :: a
+
+ -- | Transforms a type into a string. If the type cannot be marshalled, this
+ -- function should return Nothing.
+ --
+ -- If a type cannot be marshalled, it cannot persist across hot reloads.
+ marshalExtension :: a -> Maybe String
+
+ -- | Reads an extension from a string. If this type is not marshallable or
+ -- reading fails, this function should return Nothing.
+ demarshalExtension :: String -> Maybe a
+
+ -- | If the type implements Default, use the default implementation.
+ default initialValue :: (Default a) => a
+ initialValue = def
+
+ -- | If the type implements Show, use show for the marshalling.
+ default marshalExtension :: (Show a) => a -> Maybe String
+ marshalExtension = Just . show
+
+ -- | If the type implements Read, use read for the demarshalling.
+ default demarshalExtension :: (Read a) => String -> Maybe a
+ demarshalExtension = readMaybe
+
+-- | State type. This type contains changeable data.
+data State m where
+ State ::
+ { -- The datastructure containing the state of the windows.
+
+ -- | Current set of hooks. The initial hooks are provided by the
+ -- configuration, but the hooks can change during operation. This is how
+ -- key sequences can be mapped.
+ currentHooks :: Hooks m,
+ -- | Map from the typerep string to the state extension.
+ extensibleState ::
+ M.Map ExtensionKey (Either String (Extension StateExtension))
+ } ->
+ State m
+
+-- | Lookup from an extension map by type.
+lookupByType :: (Typeable a) => proxy a -> M.Map ExtensionKey b -> Maybe b
+lookupByType pxy = M.lookup (typeRepr pxy)
diff --git a/plug/src/Montis/Core/State/Marshal.hs b/plug/src/Montis/Core/State/Marshal.hs
new file mode 100644
index 0000000..642201e
--- /dev/null
+++ b/plug/src/Montis/Core/State/Marshal.hs
@@ -0,0 +1,39 @@
+module Montis.Core.State.Marshal (marshalState, demarshalState) where
+
+import Data.Map qualified as M
+import Data.Maybe (mapMaybe)
+import Montis.Core.Extensions
+import Montis.Core.State
+
+data MarshalledState where
+ MarshalledState ::
+ [(String, String)] ->
+ MarshalledState
+ deriving (Show, Read)
+
+-- | Marshals the serializable parts of the state to a string. This happens
+-- during a hot-reload where some state must be saved to persist across hot
+-- reloads.
+marshalState :: State m -> String
+marshalState
+ ( State
+ { extensibleState = xs
+ }
+ ) =
+ show $
+ MarshalledState
+ (mapMaybe (\(k, v) -> (extensionKeyValue k,) <$> doMarshalEx v) (M.toList xs))
+ where
+ doMarshalEx :: Either String (Extension StateExtension) -> Maybe String
+ doMarshalEx (Left s) = Just s
+ doMarshalEx (Right (Extension a)) = marshalExtension a
+
+-- | Demarshals the string from "marshalState" into a state. Uses the provided
+-- config to fill out non-persistent parts of the state.
+demarshalState :: Config m -> String -> State m
+demarshalState Config {startingHooks = hooks} str =
+ State hooks xs
+ where
+ ( MarshalledState
+ (M.mapKeys ExtensionKey . fmap Left . M.fromList -> xs)
+ ) = read str
diff --git a/plug/src/Montis/Core/SurfaceEvent.hs b/plug/src/Montis/Core/SurfaceEvent.hs
deleted file mode 100644
index 93bcdae..0000000
--- a/plug/src/Montis/Core/SurfaceEvent.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Montis.Core.SurfaceEvent
- ( SurfaceEvent (..),
- SurfaceState (..),
- )
-where
-
-import Montis.Foreign.WlRoots
-
-data SurfaceState = Map | Unmap | Destroy
- deriving (Eq, Ord, Show, Read, Enum)
-
-data SurfaceEvent = SurfaceEvent
- { state :: SurfaceState,
- surface :: Surface
- }
- deriving (Eq, Ord, Show)
diff --git a/plug/src/Montis/Core/W.hs b/plug/src/Montis/Core/W.hs
deleted file mode 100644
index 3aac22a..0000000
--- a/plug/src/Montis/Core/W.hs
+++ /dev/null
@@ -1,365 +0,0 @@
-{-# LANGUAGE DuplicateRecordFields #-}
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Core.W where
-
-import Control.Arrow (Arrow (first))
-import Control.Monad ((<=<))
-import Control.Monad.RWS (MonadIO (liftIO), MonadReader (..), MonadState, modify)
-import Control.Monad.Reader (ReaderT (runReaderT))
-import Control.Monad.State (StateT (runStateT), gets, modify')
-import Control.Monad.Trans.Maybe
-import Data.Data (Typeable, cast, tyConModule, tyConName, tyConPackage)
-import Data.Default.Class (Default, def)
-import Data.Kind (Constraint, Type)
-import Data.Map (Map)
-import Data.Map qualified as M
-import Data.Maybe (fromMaybe, mapMaybe)
-import Data.Proxy
-import Data.Set (Set)
-import Data.Set qualified as Set
-import Data.Void (Void)
-import Foreign (Ptr, StablePtr, intPtrToPtr, ptrToIntPtr)
-import Montis.Core.ButtonEvent (ButtonEvent)
-import Montis.Core.KeyEvent
-import Montis.Core.SurfaceEvent
-import Montis.Foreign.ForeignInterface qualified as ForeignInterface
-import Montis.Foreign.WlRoots (Surface, WlrSeat)
-import Montis.StackSet hiding (layout)
-import Montis.StackSet qualified as StackSet
-import Text.Printf (printf)
-import Text.Read hiding (lift)
-import Type.Reflection (someTypeRep, someTypeRepTyCon)
-
-data RationalRect = RationalRect Rational Rational Rational Rational
-
--- | Wrapper for a message. Messages are sent to layout and layouts are supposed
--- to handle them. This hides a typeable parameter.
-data Message where
- Message :: (Typeable a) => a -> Message
-
--- | casts a message to a type.
-fromMessage :: (Typeable a) => Message -> Maybe a
-fromMessage (Message t) = cast t
-
--- | Wraps a type in a message.
-toMessage :: (Typeable a) => a -> Message
-toMessage = Message
-
-class (Typeable l) => HandleMessage l where
- handleMessage :: Message -> l -> MaybeT W l
- handleMessage _ = return
-
-newtype Window = Window
- { surface :: Surface
- }
- deriving (Show, Ord, Eq, Read)
-
--- | Types of this class "lay out" windows by assigning rectangles and handle
--- messages.
-class (Typeable l, HandleMessage l) => LayoutClass l where
- -- | Constraints on the type to lay out. Sometimes a layout requires the 'a'
- -- type to be "Ord", other times "Eq", this is the mechanism by which this
- -- constraint is expressed.
- type LayoutConstraint l :: Type -> Constraint
-
- -- | Runs the layout in an impure way returning a modified layout and the list
- -- of windows to their rectangles under a monad.
- runLayout :: (LayoutConstraint l a) => Stack a -> l -> W (l, [(a, RationalRect)])
-
- readLayout :: String -> Maybe l
- default readLayout :: (Read l) => String -> Maybe l
- readLayout = readMaybe
-
- serializeLayout :: l -> String
- default serializeLayout :: (Show l) => l -> String
- serializeLayout = show
-
- description :: l -> String
- default description :: (Show l) => l -> String
- description = show
- {-# MINIMAL runLayout #-}
-
--- | Lifts a pure-layout implementation to a signature that complies with
--- 'runLayout'
-pureLayout ::
- (Stack a -> l -> [(a, RationalRect)]) ->
- Stack a ->
- l ->
- W (l, [(a, RationalRect)])
-pureLayout fn as l = return (l, fn as l)
-
--- A Layout which hides the layout parameter under an existential type and
--- asserts the layout hidden can work with Window types.
-data WindowLayout
- = forall l a.
- (LayoutClass l, LayoutConstraint l a, a ~ Window) =>
- WindowLayout l
-
-runWindowLayout :: Stack Window -> WindowLayout -> W (WindowLayout, [(Window, RationalRect)])
-runWindowLayout as (WindowLayout l) = first WindowLayout <$> runLayout as l
-
-handleWindowMessage :: Message -> WindowLayout -> MaybeT W WindowLayout
-handleWindowMessage m (WindowLayout l) = WindowLayout <$> handleMessage m l
-
--- | Using the 'Layout' as a witness, parse existentially wrapped windows
--- from a 'String'.
-readWindowLayout :: WindowLayout -> String -> WindowLayout
-readWindowLayout (WindowLayout l) s
- | (Just x) <- readLayout s =
- WindowLayout (asTypeOf x l)
-readWindowLayout l _ = l
-
--- | Serializes a window layout to a string.
-serializeWindowLayout :: WindowLayout -> String
-serializeWindowLayout (WindowLayout l) = serializeLayout l
-
-type ScreenId = ()
-
-type ScreenDetail = ()
-
-type Tag = String
-
-newtype ReadPtr a = ReadPtr (Ptr ())
-
-instance Read (ReadPtr a) where
- readPrec = fmap (ReadPtr . intPtrToPtr) readPrec
-
-instance Show (ReadPtr a) where
- show (ReadPtr ptr) = show (ptrToIntPtr ptr)
-
-type Montis = StablePtr (Context, State)
-
--- Read-only context under which montis is run under.
-data Context = Context
- { ctxConfig :: Config WindowLayout,
- ctxPlugin :: Ptr Void
- }
-
-defaultHooks :: Hooks
-defaultHooks =
- Hooks
- { keyHook = \_ -> return (),
- surfaceHook = handleSurface,
- buttonHook = \_ -> return ()
- }
-
-defaultConfig :: Config ()
-defaultConfig =
- Config
- { hooks = defaultHooks,
- layout = (),
- resetHook = return ()
- }
-
-data Hooks = Hooks
- { keyHook :: KeyEvent -> W (),
- surfaceHook :: SurfaceEvent -> W (),
- buttonHook :: ButtonEvent -> W ()
- }
-
-data Config l = Config
- { layout :: l,
- hooks :: Hooks,
- resetHook :: W ()
- }
-
--- | Typeclass defining the set of types which can be used as state extensions
--- to the W monad. These state extensions may be persistent or not.
---
--- There are default implementations for all methods if the type implements
--- Read, Show and Default,
-class (Typeable a) => ExtensionClass a where
- -- | The initial value used for the first time an extension is 'gotten' or
- -- demarshalling fails.
- initialValue :: a
-
- -- | Transforms a type into a string. If the type cannot be marshalled, this
- -- function should return Nothing.
- --
- -- If a type cannot be marshalled, it cannot persist across hot reloads.
- marshalExtension :: a -> Maybe String
-
- -- | Reads an extension from a string. If this type is not marshallable or
- -- reading fails, this function should return Nothing.
- demarshalExtension :: String -> Maybe a
-
- -- | If the type implements Default, use the default implementation.
- default initialValue :: (Default a) => a
- initialValue = def
-
- -- | If the type implements Show, use show for the marshalling.
- default marshalExtension :: (Show a) => a -> Maybe String
- marshalExtension = Just . show
-
- -- | If the type implements Read, use read for the demarshalling.
- default demarshalExtension :: (Read a) => String -> Maybe a
- demarshalExtension = readMaybe
-
-data StateExtension where
- StateExtension :: (ExtensionClass a) => a -> StateExtension
-
--- | Puts a state extension.
-xput :: forall a m. (ExtensionClass a, Wlike m) => a -> m ()
-xput val = liftW $ do
- modify' $ \state@State {extensibleState = extensibleState} ->
- state
- { extensibleState =
- M.insert
- ( xRepr (Proxy :: Proxy a)
- )
- (Right $ StateExtension val)
- extensibleState
- }
-
--- | Modifies a state extension.
-xmodify :: forall a m. (ExtensionClass a, Wlike m) => (a -> a) -> m ()
-xmodify fn = xput . fn =<< xget
-
--- | Modifies a state extension in the monadic context.
-xmodifyM :: forall a m. (ExtensionClass a, Wlike m) => (a -> m a) -> m ()
-xmodifyM fn = (xput <=< fn) =<< xget
-
--- | Produces a string representation of a type used to key into the extensible
--- state map.
-xRepr :: forall proxy a. (ExtensionClass a) => proxy a -> String
-xRepr _ = tyconToStr $ someTypeRepTyCon (someTypeRep (Proxy :: Proxy a))
- where
- tyconToStr tc =
- printf "%s.%s.%s" (tyConPackage tc) (tyConModule tc) (tyConName tc)
-
--- | Gets a state extension.
-xget :: forall a m. (ExtensionClass a, Wlike m) => m a
-xget = do
- xs <- liftW $ gets extensibleState
- case M.lookup (xRepr (Proxy :: Proxy a)) xs of
- Just (Right (StateExtension a)) -> return (fromMaybe initialValue (cast a))
- Just (Left str) ->
- let v = fromMaybe initialValue (demarshalExtension str)
- in xput v >> return v
- Nothing ->
- xput (initialValue :: a) >> return initialValue
-
-xgets :: forall a b m. (ExtensionClass a, Wlike m) => (a -> b) -> m b
-xgets fn = fn <$> xget
-
--- State as it is marshalled. Used for derived instances of Show and Read.
-data MarshalledState
- = MarshalledState
- (StackSet ScreenId ScreenDetail Tag String Window)
- (Set Window)
- [(String, String)]
- deriving (Show, Read)
-
-data State = State
- { -- The datastructure containing the state of the windows.
- mapped :: StackSet ScreenId ScreenDetail Tag WindowLayout Window,
- -- | All the windows wetterhorn knows about, even if they are not mapped.
- allWindows :: Set Window,
- -- | Current set of hooks. The initial hooks are provided by the
- -- configuration, but the hooks can change during operation. This is how key
- -- sequences can be mapped.
- currentHooks :: Hooks,
- -- | Map from the typerep string to the state extension.
- extensibleState :: Map String (Either String StateExtension)
- }
-
--- | Initializes a "cold" state from a configuration. A cold state is the
--- initial state on startup. It is constrasted with a "hot" state, which is a
--- persisted state after a hot-reload.
-initColdState :: Config WindowLayout -> IO State
-initColdState Config {layout = layout, hooks = hooks} =
- return $
- State
- ( StackSet (Screen () () (Workspace "0" layout (Stack [] []))) [] []
- )
- mempty
- hooks
- mempty
-
--- | Marshals the serializable parts of the state to a string. This happens
--- during a hot-reload where some state must be saved to persist across hot
--- reloads.
-marshalState :: State -> String
-marshalState
- ( State
- { mapped = mapped,
- allWindows = allWindows,
- extensibleState = xs
- }
- ) =
- show $
- MarshalledState
- (mapLayout serializeWindowLayout mapped)
- allWindows
- (mapMaybe (\(k, v) -> (k,) <$> doMarshalEx v) (M.toList xs))
- where
- doMarshalEx (Left s) = Just s
- doMarshalEx (Right (StateExtension a)) = marshalExtension a
-
--- | Demarshals the string from "marshalState" into a state. Uses the provided
--- config to fill out non-persistent parts of the state.
-demarshalState :: Config WindowLayout -> String -> State
-demarshalState Config {hooks = hooks, layout = layout} str =
- State mapped allWindows hooks xs
- where
- ( MarshalledState
- (mapLayout (readWindowLayout layout) -> mapped)
- allWindows
- (fmap Left . M.fromList -> xs)
- ) = read str
-
--- | This is _the_ main monad used for Montis operations. Contains
--- everything required to operate. Contains the state, configuration and
--- interface to foreign code.
-newtype W a = W (ReaderT Context (StateT State IO) a)
- deriving (Functor, Applicative, Monad, MonadState State, MonadIO)
-
--- | Let Config be the thing W is a reader for. There is already a way to get
--- the foreign interface in the context.
-instance MonadReader (Config WindowLayout) W where
- local fn (W r) = W $ local (\(Context conf plug) -> Context (fn conf) plug) r
- ask = W $ ctxConfig <$> ask
-
-runW :: W a -> (Context, State) -> IO (a, State)
-runW (W fn) (ctx, st) = runStateT (runReaderT fn ctx) st
-
-wio :: IO a -> W a
-wio = liftIO
-
--- | Type class to lift an arbitrary 'W' computation into another monad.
-class (Monad m) => Wlike m where
- liftW :: W a -> m a
-
--- | Trivial instance of W for Wlike.
-instance Wlike W where
- liftW = id
-
--- Default implementations for common handlers.
-
--- | handles a new surface event. This updates the state to reflect how it
--- should look in the harness.
-handleSurface :: SurfaceEvent -> W ()
-handleSurface (SurfaceEvent state (Window -> win)) =
- case state of
- Destroy ->
- modify $
- \st@State
- { allWindows = allWindows,
- mapped = mapped
- } ->
- st
- { allWindows = Set.delete win allWindows,
- mapped = StackSet.delete win mapped
- }
- Unmap -> modify $
- \st@State {mapped = mapped} ->
- st
- { mapped = StackSet.delete win mapped
- }
- Map -> modify $
- \st@State {mapped = mapped, allWindows = allWindows} ->
- st
- { mapped = StackSet.insertTiled win mapped,
- allWindows = Set.insert win allWindows
- }
diff --git a/plug/src/Montis/Dsl/Bind.hs b/plug/src/Montis/Dsl/Bind.hs
deleted file mode 100644
index 8d4e173..0000000
--- a/plug/src/Montis/Dsl/Bind.hs
+++ /dev/null
@@ -1,129 +0,0 @@
-{-# OPTIONS_GHC -Wno-unused-top-binds #-}
-
--- | eDSL for the 'bind' function. The 'bind' function provides an easy way to
--- bind certain actions to other actions.
-module Montis.Dsl.Bind
- ( bind,
- (.+),
- MatchEvent (..),
- MatchModifiers (..),
- Modifier (..),
- released,
- weak,
- run,
- modifierToMask,
- module X,
- )
-where
-
-import Control.Monad
-import Data.Bits
-import Data.Word
-import Montis.Core.ButtonEvent (ButtonEvent (..))
-import qualified Montis.Core.ButtonEvent as ButtonEvent
-import Montis.Core.KeyEvent (KeyEvent (..))
-import qualified Montis.Core.KeyEvent as KeyEvent
-import Montis.Core.W
-import Montis.Dsl.Buttons as X
-import Montis.Dsl.Input
-
-class MatchEvent m where
- matches :: m -> InputEvent -> W Bool
-
-instance MatchEvent (InputEvent -> W Bool) where
- matches = ($)
-
-instance MatchEvent Char where
- matches ch (InputKeyEvent ke) = return $ KeyEvent.codepoint ke == ch
- matches _ _ = return False
-
-instance MatchEvent Button where
- matches (Button b) (InputButtonEvent be) =
- return $ ButtonEvent.button be == b
- matches _ _ = return False
-
--- | Enumeration of possible modifiers.
---
--- ModX can be used for extra user-defined modifiers which are not standard xkb
--- modifiers.
-data Modifier
- = Shift
- | Lock
- | Control
- | Mod1
- | Mod2
- | Mod3
- | Mod4
- | Mod5
- | ModX Int
- deriving (Eq, Ord, Show, Read)
-
--- | Converts a modifier to its associated mask.
-modifierToMask :: Modifier -> Word32
-modifierToMask m =
- 1
- `shiftL` case m of
- Shift -> 0
- Lock -> 1
- Control -> 2
- Mod1 -> 3
- Mod2 -> 4
- Mod3 -> 5
- Mod4 -> 6
- Mod5 -> 7
- ModX b -> b + 8
-
-released :: (MatchEvent m) => m -> InputEvent -> W Bool
-released me ev | not (isPressEvent ev) = matches me ev
-released _ _ = return False
-
-data MatchModifiers = MatchModifiers
- { weakModifierMatch :: Bool,
- modifierMask :: Word32,
- baseMatch :: InputEvent -> W Bool
- }
-
-instance MatchEvent MatchModifiers where
- matches (MatchModifiers weakMatch bits base) ev = do
- mods <- getMods ev
- b <- liftW $ base ev
-
- return $
- b
- && ( (not weakMatch && mods == bits)
- || (weakMatch && (bits .&. mods == bits))
- )
- where
- getMods (InputButtonEvent (ButtonEvent {ButtonEvent.modifiers = mods})) = return mods
- getMods (InputKeyEvent (KeyEvent {KeyEvent.modifiers = mods})) = return mods
-
-class LiftMatchModifiers a where
- toModifiers :: a -> MatchModifiers
- default toModifiers :: (MatchEvent a) => a -> MatchModifiers
- toModifiers = MatchModifiers False 0 . matches
-
-instance LiftMatchModifiers MatchModifiers where
- toModifiers = id
-
-instance LiftMatchModifiers Char
-
-instance LiftMatchModifiers Button
-
--- toModifiers ch = MatchModifiers False 0 (matches ch)
-
-(.+) :: (LiftMatchModifiers mods) => Modifier -> mods -> MatchModifiers
-(.+) modifier (toModifiers -> (MatchModifiers b mask base)) =
- MatchModifiers b (mask .|. modifierToMask modifier) base
-
-infixr 9 .+
-
-bind :: (MatchEvent match) => InputEvent -> match -> InputM spy () -> InputM spy ()
-bind ev match action = do
- matches' <- liftW $ matches match ev
- when matches' (action >> continue)
-
-weak :: MatchModifiers -> MatchModifiers
-weak m = m {weakModifierMatch = True}
-
-run :: W () -> InputM spy ()
-run = liftW
diff --git a/plug/src/Montis/Dsl/Buttons.hsc b/plug/src/Montis/Dsl/Buttons.hsc
deleted file mode 100644
index a73ccc6..0000000
--- a/plug/src/Montis/Dsl/Buttons.hsc
+++ /dev/null
@@ -1,230 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-module Montis.Dsl.Buttons where
-
-import Data.Word
-
-#include </usr/include/linux/input-event-codes.h>
-
-data Button = Button Word32
-
-btnMisc :: Button
-btnMisc = Button #const BTN_MISC
-
-btn0 :: Button
-btn0 = Button #const BTN_0
-
-btn1 :: Button
-btn1 = Button #const BTN_1
-
-btn2 :: Button
-btn2 = Button #const BTN_2
-
-btn3 :: Button
-btn3 = Button #const BTN_3
-
-btn4 :: Button
-btn4 = Button #const BTN_4
-
-btn5 :: Button
-btn5 = Button #const BTN_5
-
-btn6 :: Button
-btn6 = Button #const BTN_6
-
-btn7 :: Button
-btn7 = Button #const BTN_7
-
-btn8 :: Button
-btn8 = Button #const BTN_8
-
-btn9 :: Button
-btn9 = Button #const BTN_9
-
-btnMouse :: Button
-btnMouse = Button #const BTN_MOUSE
-
-btnLeft :: Button
-btnLeft = Button #const BTN_LEFT
-
-btnRight :: Button
-btnRight = Button #const BTN_RIGHT
-
-btnMiddle :: Button
-btnMiddle = Button #const BTN_MIDDLE
-
-btnSide :: Button
-btnSide = Button #const BTN_SIDE
-
-btnExtra :: Button
-btnExtra = Button #const BTN_EXTRA
-
-btnForward :: Button
-btnForward = Button #const BTN_FORWARD
-
-btnBack :: Button
-btnBack = Button #const BTN_BACK
-
-btnTask :: Button
-btnTask = Button #const BTN_TASK
-
-btnJoystick :: Button
-btnJoystick = Button #const BTN_JOYSTICK
-
-btnTrigger :: Button
-btnTrigger = Button #const BTN_TRIGGER
-
-btnThumb :: Button
-btnThumb = Button #const BTN_THUMB
-
-btnThumb2 :: Button
-btnThumb2 = Button #const BTN_THUMB2
-
-btnTop :: Button
-btnTop = Button #const BTN_TOP
-
-btnTop2 :: Button
-btnTop2 = Button #const BTN_TOP2
-
-btnPinkie :: Button
-btnPinkie = Button #const BTN_PINKIE
-
-btnBase :: Button
-btnBase = Button #const BTN_BASE
-
-btnBase2 :: Button
-btnBase2 = Button #const BTN_BASE2
-
-btnBase3 :: Button
-btnBase3 = Button #const BTN_BASE3
-
-btnBase4 :: Button
-btnBase4 = Button #const BTN_BASE4
-
-btnBase5 :: Button
-btnBase5 = Button #const BTN_BASE5
-
-btnBase6 :: Button
-btnBase6 = Button #const BTN_BASE6
-
-btnDead :: Button
-btnDead = Button #const BTN_DEAD
-
-btnGamepad :: Button
-btnGamepad = Button #const BTN_GAMEPAD
-
-btnSouth :: Button
-btnSouth = Button #const BTN_SOUTH
-
-btnA :: Button
-btnA = Button #const BTN_A
-
-btnEast :: Button
-btnEast = Button #const BTN_EAST
-
-btnB :: Button
-btnB = Button #const BTN_B
-
-btnC :: Button
-btnC = Button #const BTN_C
-
-btnNorth :: Button
-btnNorth = Button #const BTN_NORTH
-
-btnX :: Button
-btnX = Button #const BTN_X
-
-btnWest :: Button
-btnWest = Button #const BTN_WEST
-
-btnY :: Button
-btnY = Button #const BTN_Y
-
-btnZ :: Button
-btnZ = Button #const BTN_Z
-
-btnTl :: Button
-btnTl = Button #const BTN_TL
-
-btnTr :: Button
-btnTr = Button #const BTN_TR
-
-btnTl2 :: Button
-btnTl2 = Button #const BTN_TL2
-
-btnTr2 :: Button
-btnTr2 = Button #const BTN_TR2
-
-btnSelect :: Button
-btnSelect = Button #const BTN_SELECT
-
-btnStart :: Button
-btnStart = Button #const BTN_START
-
-btnMode :: Button
-btnMode = Button #const BTN_MODE
-
-btnThumbl :: Button
-btnThumbl = Button #const BTN_THUMBL
-
-btnThumbr :: Button
-btnThumbr = Button #const BTN_THUMBR
-
-btnDigi :: Button
-btnDigi = Button #const BTN_DIGI
-
-btnToolPen :: Button
-btnToolPen = Button #const BTN_TOOL_PEN
-
-btnToolRubber :: Button
-btnToolRubber = Button #const BTN_TOOL_RUBBER
-
-btnToolBrush :: Button
-btnToolBrush = Button #const BTN_TOOL_BRUSH
-
-btnToolPencil :: Button
-btnToolPencil = Button #const BTN_TOOL_PENCIL
-
-btnToolAirbrush :: Button
-btnToolAirbrush = Button #const BTN_TOOL_AIRBRUSH
-
-btnToolFinger :: Button
-btnToolFinger = Button #const BTN_TOOL_FINGER
-
-btnToolMouse :: Button
-btnToolMouse = Button #const BTN_TOOL_MOUSE
-
-btnToolLens :: Button
-btnToolLens = Button #const BTN_TOOL_LENS
-
-btnToolQuinttap :: Button
-btnToolQuinttap = Button #const BTN_TOOL_QUINTTAP
-
-btnStylus3 :: Button
-btnStylus3 = Button #const BTN_STYLUS3
-
-btnTouch :: Button
-btnTouch = Button #const BTN_TOUCH
-
-btnStylus :: Button
-btnStylus = Button #const BTN_STYLUS
-
-btnStylus2 :: Button
-btnStylus2 = Button #const BTN_STYLUS2
-
-btnToolDoubletap :: Button
-btnToolDoubletap = Button #const BTN_TOOL_DOUBLETAP
-
-btnToolTripletap :: Button
-btnToolTripletap = Button #const BTN_TOOL_TRIPLETAP
-
-btnToolQuadtap :: Button
-btnToolQuadtap = Button #const BTN_TOOL_QUADTAP
-
-btnWheel :: Button
-btnWheel = Button #const BTN_WHEEL
-
-btnGearDown :: Button
-btnGearDown = Button #const BTN_GEAR_DOWN
-
-btnGearUp :: Button
-btnGearUp = Button #const BTN_GEAR_UP
diff --git a/plug/src/Montis/Dsl/Input.hs b/plug/src/Montis/Dsl/Input.hs
deleted file mode 100644
index 1ead0c7..0000000
--- a/plug/src/Montis/Dsl/Input.hs
+++ /dev/null
@@ -1,284 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-
-module Montis.Dsl.Input
- ( InputM,
- InputEvent (..),
- InputProxy (..),
- NoProxy,
- withProxies,
- forwardEvent,
- forwardKey,
- whenKeyEvent,
- whenButtonEvent,
- useInputHandler,
- unwrap,
- filterEvent,
- isPressEvent,
- nextInputEventThat,
- replayEvents,
- isKeyEvent,
- nextInputPressEvent,
- continue,
- nextInputEvent,
- getModifierState,
- )
-where
-
-import Control.Monad
-import Control.Monad.Cont (MonadCont)
-import Control.Monad.Loops (andM)
-import Control.Monad.RWS
- ( MonadIO (liftIO),
- MonadReader (ask),
- MonadState (get),
- MonadTrans (lift),
- RWST,
- execRWST,
- gets,
- modify,
- )
-import Control.Monad.Trans.Cont
-import Control.Monad.Trans.Maybe (MaybeT (runMaybeT))
-import Data.IORef (newIORef, readIORef, writeIORef)
-import Data.Proxy
-import Data.Word (Word32)
-import Montis.Core.ButtonEvent qualified as ButtonEvent
-import Montis.Core.KeyEvent qualified as KeyEvent
-import Montis.Core.W (W (..))
-import Montis.Core.W qualified as W
-import Montis.Foreign.WlRoots (guardNull, wlrKeyboardGetModifiers, wlrSeatGetKeyboard, wlrSeatKeyboardNotifyKey, wlrSeatSetKeyboard)
-import Montis.Core.Runtime (getSeat)
-
-class InputProxy (spy :: k) where
- onKeyEvent :: Proxy spy -> InputEvent -> MaybeT W InputEvent
-
-instance (InputProxy h, InputProxy t) => InputProxy (h ': t) where
- onKeyEvent _ = onKeyEvent (Proxy :: Proxy h) <=< onKeyEvent (Proxy :: Proxy t)
-
-instance InputProxy '[] where
- onKeyEvent _ = return
-
-data NoProxy
-
-instance InputProxy NoProxy where
- onKeyEvent _ = return
-
-instance (InputProxy s1, InputProxy s2) => InputProxy (s1, s2) where
- onKeyEvent proxy = onKeyEvent (fmap fst proxy) <=< onKeyEvent (fmap snd proxy)
-
--- | Union of event types.
-data InputEvent
- = InputButtonEvent ButtonEvent.ButtonEvent
- | InputKeyEvent KeyEvent.KeyEvent
-
--- | Context for the input.
-newtype InputContext spy = InputContext
- { -- | Top of the input routine. Used in "continue" statement.
- inputTop :: InputM spy ()
- }
-
-newtype InputState spy = InputState
- { inputSource :: InputM spy InputEvent
- }
-
--- | Input monad for handling all kinds of input.
-newtype InputM spy a = InputM (ContT () (RWST (InputContext spy) () (InputState spy) W) a)
- deriving (Monad, Functor, Applicative, MonadCont, MonadIO)
-
-instance MonadFail (InputM spy) where
- fail _ = continue
-
--- | Lifts a W action to an InputM action.
-instance W.Wlike (InputM spy) where
- liftW = InputM . lift . lift
-
--- | Resets the input handler to the top.
-continue :: InputM spy a
-continue = do
- (InputContext {inputTop = (InputM top)}) <- InputM ask
- InputM $ shiftT (\_ -> resetT top)
-
--- | Forwards the given key event to the focused window.
-forwardKey :: KeyEvent.KeyEvent -> W ()
-forwardKey keyEvent = do
- seatPtr <- getSeat
- W.wio $ do
- wlrSeatSetKeyboard
- seatPtr
- (KeyEvent.device keyEvent)
-
- wlrSeatKeyboardNotifyKey
- seatPtr
- (KeyEvent.timeMs keyEvent)
- (KeyEvent.keycode keyEvent)
- ( case KeyEvent.state keyEvent of
- KeyEvent.KeyReleased -> 0
- _ -> 1
- )
-
--- | Executes a function if the input event is a key event. If it is not a key
--- event, then nothing happens.
-whenKeyEvent :: (Monad m) => InputEvent -> (KeyEvent.KeyEvent -> m ()) -> m ()
-whenKeyEvent (InputKeyEvent ke) = ($ ke)
-whenKeyEvent _ = const (return ())
-
--- | Executes a function in the input event is a button event. If it is not a
--- button event, then nothing happens.
-whenButtonEvent ::
- (Monad m) => InputEvent -> (ButtonEvent.ButtonEvent -> m ()) -> m ()
-whenButtonEvent (InputButtonEvent be) = ($ be)
-whenButtonEvent _ = const (return ())
-
--- | Forwards the given input event to focused window.
-forwardEvent :: (W.Wlike m) => InputEvent -> m ()
-forwardEvent = \case
- InputKeyEvent kv -> W.liftW $ forwardKey kv
- InputButtonEvent _ -> return ()
-
--- | "Unwraps" a maybe. If the maybe is present, the handler proceeds. If the
--- maybe is not present, the handler restarts execution from the top.
-unwrap :: Maybe a -> InputM spy a
-unwrap (Just val) = return val
-unwrap Nothing = continue
-
--- | Runs the series of events from the top as if they were input.
-replayEvents :: [InputEvent] -> InputM spy ()
-replayEvents events = do
- ioref <- liftIO (newIORef events)
-
- (InputM oldInput) <- InputM $ gets inputSource
-
- let newInput =
- InputM $
- shiftT
- ( \thingToDo -> do
- r <- liftIO (readIORef ioref)
- case r of
- [] -> do
- modify $ \st -> st {inputSource = InputM oldInput}
- a <- oldInput
- lift (thingToDo a)
- (a : as) -> do
- liftIO (writeIORef ioref as)
- lift (thingToDo a)
- )
-
- InputM $ modify $ \st -> st {inputSource = newInput}
-
--- | Call in the reset handler with the InputM handler you wolud like to use.
-useInputHandler :: (InputProxy spy) => InputM spy () -> W ()
-useInputHandler (forever -> top@(InputM ctop)) = do
- void $ execRWST (runContT ctop return) (InputContext top) (InputState useSeatEvents)
-
--- | Returns the next input event that's either a kep press or a button press.
-nextInputPressEvent :: InputM spy InputEvent
-nextInputPressEvent = nextInputEventThat (andM [isPressEvent, not . modifierKey])
-
-modifierKey :: InputEvent -> Bool
-modifierKey (InputKeyEvent (KeyEvent.KeyEvent {codepoint = '\NUL'})) = True
-modifierKey _ = False
-
-nextInputEventThat :: (InputEvent -> Bool) -> InputM spy InputEvent
-nextInputEventThat fn =
- nextInputEvent
- >>= ( \ie ->
- if fn ie
- then return ie
- else forwardEvent ie >> nextInputEventThat fn
- )
-
-isKeyEvent :: InputEvent -> Bool
-isKeyEvent (InputKeyEvent _) = True
-isKeyEvent _ = False
-
-isPressEvent :: InputEvent -> Bool
-isPressEvent (InputButtonEvent be)
- | ButtonEvent.state be == ButtonEvent.ButtonPressed =
- True
-isPressEvent (InputKeyEvent ke)
- | KeyEvent.state ke == KeyEvent.KeyPressed =
- True
-isPressEvent _ = False
-
--- | Returns the event only if it matches the filter. If it does not match the
--- filter, execution resets to the top.
-filterEvent :: (InputEvent -> Bool) -> InputEvent -> InputM spy InputEvent
-filterEvent fn ev | fn ev = return ev
-filterEvent _ _ = continue
-
-getModifierState :: W Word32
-getModifierState = do
- seat <- getSeat
- keyboard <- W.wio $ wlrSeatGetKeyboard seat
- maybe (return 0) (W.wio . wlrKeyboardGetModifiers) (guardNull keyboard)
-
-nextInputEvent :: InputM spy InputEvent
-nextInputEvent = join $ InputM $ gets inputSource
-
-withProxies :: Proxy spy -> InputM spy a -> InputM spy a
-withProxies _ = id
-
--- | Gets the next input event.
-useSeatEvents :: forall spy. (InputProxy spy) => InputM spy InputEvent
-useSeatEvents =
- InputM $
- shiftT
- ( \thingToDo -> do
- putButtonHandler $ \be -> do
- runSpies thingToDo (InputButtonEvent be)
-
- putKeyHandler $ \ke -> do
- runSpies thingToDo (InputKeyEvent ke)
- )
- where
- runSpies fn ev = do
- evM <- lift $ runMaybeT (onKeyEvent (Proxy :: Proxy spy) ev)
- mapM_
- ( \ev' -> do
- clearButtonHandler
- clearKeyHandler
- fn ev'
- )
- evM
-
- clearButtonHandler =
- lift $
- modify $ \st ->
- st
- { W.currentHooks =
- (W.currentHooks st)
- { W.buttonHook = const (return ())
- }
- }
-
- clearKeyHandler =
- lift $
- modify $ \st ->
- st
- { W.currentHooks =
- (W.currentHooks st)
- { W.keyHook = const (return ())
- }
- }
-
- putButtonHandler h = lift $ do
- (r, s) <- (,) <$> ask <*> get
- lift $
- modify $ \st ->
- st
- { W.currentHooks =
- (W.currentHooks st)
- { W.buttonHook = \be -> void (execRWST (h be) r s)
- }
- }
-
- putKeyHandler h = lift $ do
- (r, s) <- (,) <$> ask <*> get
- lift $
- modify $ \st ->
- st
- { W.currentHooks =
- (W.currentHooks st)
- { W.keyHook = \ke -> void (execRWST (h ke) r s)
- }
- }
diff --git a/plug/src/Montis/Foreign.hs b/plug/src/Montis/Foreign.hs
deleted file mode 100644
index fbbfb08..0000000
--- a/plug/src/Montis/Foreign.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Montis.Foreign
- ( TypedIntPtr (..),
- toPtr,
- fromPtr,
- )
-where
-
-import Foreign (IntPtr, Ptr)
-import qualified Foreign
-
-toPtr :: TypedIntPtr a -> Ptr a
-toPtr (TypedIntPtr ip) = Foreign.intPtrToPtr ip
-
-fromPtr :: Ptr a -> TypedIntPtr a
-fromPtr = TypedIntPtr . Foreign.ptrToIntPtr
-
-newtype TypedIntPtr a = TypedIntPtr IntPtr
- deriving (Show, Read, Eq, Ord, Num)
diff --git a/plug/src/Montis/Foreign/Export.hs b/plug/src/Montis/Foreign/Export.hs
deleted file mode 100644
index bb8efeb..0000000
--- a/plug/src/Montis/Foreign/Export.hs
+++ /dev/null
@@ -1,209 +0,0 @@
-{-# OPTIONS_GHC -Wno-unused-top-binds #-}
-
--- | This module does not export anything. It exists simply to provide C-symbols
--- for the plugin.
-module Montis.Foreign.Export () where
-
-import Config
-import Control.Arrow (Arrow (first))
-import Control.Monad (forM_)
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Char8 as CH
-import Foreign
- ( Ptr,
- Storable (poke, pokeByteOff),
- Word32,
- Word8,
- deRefStablePtr,
- freeStablePtr,
- mallocBytes,
- newStablePtr,
- )
-import Foreign.C (CChar, CInt (..))
-import Montis.Core.ButtonEvent (ButtonEvent (ButtonEvent), ButtonState (ButtonPressed, ButtonReleased))
-import Montis.Core.KeyEvent (KeyEvent (..), KeyState (..))
-import Montis.Core.SurfaceEvent (SurfaceEvent (SurfaceEvent))
-import Montis.Core.W (Montis, W)
-import qualified Montis.Core.W as W
-import Montis.Foreign.ForeignInterface
-import Montis.Foreign.WlRoots
-import Data.Void (Void)
-
-type Wetter = (W.Config W.WindowLayout, W.State)
-
-toWetter :: (W.Context, W.State) -> (W.Config W.WindowLayout, W.State)
-toWetter = first W.ctxConfig
-
-runForeign :: (Wetter -> W ()) -> Montis -> IO Montis
-runForeign fn stblptr = do
- w@(ctx, st) <- deRefStablePtr stblptr
- freeStablePtr stblptr
- (_, state') <- W.runW (fn $ toWetter w) (ctx, st)
- newStablePtr (ctx, state')
-
-runForeignWithReturn ::
- (Storable a) => (Wetter -> W a) -> Ptr a -> Montis -> IO Montis
-runForeignWithReturn fn ptr stableptr = do
- w@(ctx, st) <- deRefStablePtr stableptr
- freeStablePtr stableptr
- (val, state') <- W.runW (fn $ toWetter w) (ctx, st)
- poke ptr val
- newStablePtr (ctx, state')
-
-runForeignWithReturn2 ::
- (Storable a, Storable b) =>
- (Wetter -> W (a, b)) ->
- Ptr a ->
- Ptr b ->
- Montis ->
- IO Montis
-runForeignWithReturn2 fn ptrA ptrB stableptr = do
- w@(ctx, st) <- deRefStablePtr stableptr
- freeStablePtr stableptr
- ((vA, vB), state') <- W.runW (fn $ toWetter w) (ctx, st)
- poke ptrA vA
- poke ptrB vB
- newStablePtr (ctx, state')
-
--- | This function is the implementation of the "hotstart" mechanism. It gives a
--- pointer to the previously marshalled state and the length of that array and
--- this function returns a Montis instance.
-foreign export ccall "plugin_hot_start"
- pluginHotStart ::
- Ptr Void -> Ptr CChar -> Word32 -> IO Montis
-
-pluginHotStart :: Ptr Void -> Ptr CChar -> Word32 -> IO Montis
-pluginHotStart self chars len = do
- bs <- BS.packCStringLen (chars, fromIntegral len)
- wtr <-
- newStablePtr
- ( W.Context config self,
- W.demarshalState config (CH.unpack bs)
- )
- runForeign (\(conf, _) -> W.resetHook conf) wtr
-
--- | This function is called when a "coldstart" request is receieved. It just
--- calles the function "wetterhorn". This function should be defined in the main
--- code as it's sort-of the equivalent of XMonad's "main" function.
-foreign export ccall "plugin_cold_start"
- pluginColdStart :: Ptr Void -> IO Montis
-
-pluginColdStart :: Ptr Void -> IO Montis
-pluginColdStart self = do
- state <- W.initColdState config
- wtr <- newStablePtr (W.Context config self, state)
- runForeign (\(conf, _) -> W.resetHook conf) wtr
-
--- | Marshals the opaque state to a C-style byte array and size pointer.
-foreign export ccall "plugin_marshal_state"
- pluginMarshalState :: Montis -> Ptr Word32 -> IO (Ptr Word8)
-
-pluginMarshalState :: Montis -> Ptr Word32 -> IO (Ptr Word8)
-pluginMarshalState stblptr outlen = do
- (_, st) <- deRefStablePtr stblptr
- let bs = CH.pack (W.marshalState st)
- ret <- mallocBytes (BS.length bs)
- poke outlen (fromIntegral $ BS.length bs)
- forM_ (zip [0 ..] (BS.unpack bs)) $ \(off, w8) -> do
- pokeByteOff ret off w8
- return ret
-
-foreign export ccall "plugin_handle_button"
- pluginHandleButton :: Ptr WlrPointerButtonEvent -> Word32 -> Montis -> IO Montis
-
-pluginHandleButton :: Ptr WlrPointerButtonEvent -> Word32 -> Montis -> IO Montis
-pluginHandleButton eventPtr modifiers = do
- runForeign $
- \( _,
- W.State {W.currentHooks = W.Hooks {buttonHook = buttonHook}}
- ) -> do
- event <- W.wio $
- runForeignDemarshal eventPtr $ do
- ButtonEvent
- <$> demarshal
- <*> demarshal
- <*> demarshal
- <*> pure modifiers
- <*> ( ( \u8 ->
- if (u8 :: Word8) == 0
- then ButtonReleased
- else ButtonPressed
- )
- <$> demarshal
- )
-
- buttonHook event
-
-foreign export ccall "plugin_handle_keybinding"
- pluginHandleKeybinding ::
- Ptr WlrInputDevice ->
- Ptr WlrEventKeyboardKey ->
- Word32 ->
- Word32 ->
- Word32 ->
- Ptr CInt ->
- Montis ->
- IO Montis
-
-pluginHandleKeybinding ::
- Ptr WlrInputDevice ->
- Ptr WlrEventKeyboardKey ->
- Word32 ->
- Word32 ->
- Word32 ->
- Ptr CInt ->
- Montis ->
- IO Montis
-pluginHandleKeybinding inputDevicePtr eventPtr mods sym cp =
- runForeignWithReturn $
- \( _,
- W.State {W.currentHooks = W.Hooks {keyHook = keyHook}}
- ) -> do
- event <- W.wio $
- runForeignDemarshal eventPtr $ do
- tMs <- demarshal
- kc <- demarshal
- _ <- (demarshal :: ForeignDemarshal Word32)
- keyState <- demarshal
- return $
- KeyEvent
- tMs
- kc
- (if keyState == (0 :: Word8) then KeyReleased else KeyPressed)
- mods
- sym
- (toEnum $ fromIntegral cp)
- inputDevicePtr
- keyHook event
- return 1
-
--- | Function exported to the harness to handle the mapping/unmapping/deletion
--- of an XDG surface.
-foreign export ccall "plugin_handle_surface"
- pluginHandleSurface ::
- Ptr WlrXdgSurface -> CInt -> Montis -> IO Montis
-
-pluginHandleSurface :: Ptr WlrXdgSurface -> CInt -> Montis -> IO Montis
-pluginHandleSurface p t =
- runForeign
- ( \(_, W.State {currentHooks = W.Hooks {surfaceHook = surfaceHook}}) ->
- surfaceHook $
- SurfaceEvent (toEnum $ fromIntegral t) (toSurface p)
- )
-
--- | Function exported to the harness to handle the mapping/unmapping/deletion
--- of an XWayland surface.
-foreign export ccall "plugin_handle_xwayland_surface"
- pluginHandleXWaylandSurface ::
- Ptr WlrXWaylandSurface -> CInt -> Montis -> IO Montis
-
-pluginHandleXWaylandSurface ::
- Ptr WlrXWaylandSurface -> CInt -> Montis -> IO Montis
-pluginHandleXWaylandSurface p t =
- runForeign
- ( \( _,
- W.State
- { currentHooks = W.Hooks {surfaceHook = surfaceHook}
- }
- ) -> surfaceHook $ SurfaceEvent (toEnum $ fromIntegral t) (toSurface p)
- )
diff --git a/plug/src/Montis/Foreign/ForeignInterface.hs b/plug/src/Montis/Foreign/ForeignInterface.hs
deleted file mode 100644
index 647fb98..0000000
--- a/plug/src/Montis/Foreign/ForeignInterface.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Montis.Foreign.ForeignInterface
- ( ForeignDemarshal (..),
- runForeignDemarshal,
- demarshal,
- )
-where
-
-import Control.Monad.State (MonadState (get, put), MonadTrans (lift), StateT, evalStateT)
-import Foreign (Ptr, Storable (peek, sizeOf), castPtr, plusPtr)
-
-newtype ForeignDemarshal a = ForeignDemarshal (StateT (Ptr ()) IO a)
- deriving (Functor, Monad, Applicative, MonadState (Ptr ()))
-
-runForeignDemarshal :: Ptr b -> ForeignDemarshal a -> IO a
-runForeignDemarshal p (ForeignDemarshal dm) = evalStateT dm (castPtr p)
-
-demarshal :: (Storable a) => ForeignDemarshal a
-demarshal = do
- ptr <- get
- val <- ForeignDemarshal $ lift $ peek $ castPtr ptr
- put (plusPtr ptr (sizeOf val))
- return val
diff --git a/plug/src/Montis/Foreign/WlRoots.hs b/plug/src/Montis/Foreign/WlRoots.hs
deleted file mode 100644
index c4adaf8..0000000
--- a/plug/src/Montis/Foreign/WlRoots.hs
+++ /dev/null
@@ -1,72 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Foreign.WlRoots where
-
-import Foreign (IntPtr, Ptr, Word32, intPtrToPtr, nullPtr, ptrToIntPtr)
-import Text.Read
-
-data WlrKeyboard
-
-data WlrPointer
-
-data WlrPointerButtonEvent
-
-data WlrSeat
-
-data WlrInputDevice
-
-data WlrEventKeyboardKey
-
-data WlrXdgSurface
-
-data WlrXWaylandSurface
-
-data Surface
- = XdgSurface (Ptr WlrXdgSurface)
- | XWaylandSurface (Ptr WlrXWaylandSurface)
- deriving (Ord, Eq)
-
-instance Show Surface where
- show (XdgSurface p) = show (XdgSerializeSurface (ptrToIntPtr p))
- show (XWaylandSurface p) = show (XWaylandSerializeSurface (ptrToIntPtr p))
-
-instance Read Surface where
- readPrec = fmap toSurf readPrec
- where
- toSurf (XdgSerializeSurface ip) = XdgSurface (intPtrToPtr ip)
- toSurf (XWaylandSerializeSurface ip) = XWaylandSurface (intPtrToPtr ip)
-
--- | Type which exists specifically to derive instances of read and show.
-data SerializableSurface
- = XdgSerializeSurface IntPtr
- | XWaylandSerializeSurface IntPtr
- deriving (Read, Show)
-
-class ForeignSurface a where
- toSurface :: Ptr a -> Surface
-
-instance ForeignSurface WlrXdgSurface where
- toSurface = XdgSurface
-
-instance ForeignSurface WlrXWaylandSurface where
- toSurface = XWaylandSurface
-
-guardNull :: Ptr a -> Maybe (Ptr a)
-guardNull p | p == nullPtr = Nothing
-guardNull p = Just p
-
-foreign import ccall "wlr_seat_set_keyboard"
- wlrSeatSetKeyboard ::
- Ptr WlrSeat -> Ptr WlrInputDevice -> IO ()
-
-foreign import ccall "wlr_seat_get_keyboard"
- wlrSeatGetKeyboard ::
- Ptr WlrSeat -> IO (Ptr WlrKeyboard)
-
-foreign import ccall "wlr_keyboard_get_modifiers"
- wlrKeyboardGetModifiers ::
- Ptr WlrKeyboard -> IO Word32
-
-foreign import ccall "wlr_seat_keyboard_notify_key"
- wlrSeatKeyboardNotifyKey ::
- Ptr WlrSeat -> Word32 -> Word32 -> Word32 -> IO ()
diff --git a/plug/src/Montis/Keys/Macros.hs b/plug/src/Montis/Keys/Macros.hs
deleted file mode 100644
index 37f4db4..0000000
--- a/plug/src/Montis/Keys/Macros.hs
+++ /dev/null
@@ -1,145 +0,0 @@
--- There are constraints used for better type-level enforced safety rules.
-{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
-
-module Montis.Keys.Macros
- ( MacroSupport,
- macroStartStopKeybind,
- macroReplayKeybind,
- stopMacroRecording,
- startRecording,
- )
-where
-
-import Control.Monad.IO.Class (MonadIO (liftIO))
-import Control.Monad.Trans (MonadTrans (lift))
-import Data.Default.Class
-import Data.Map (Map)
-import qualified Data.Map as Map
-import Data.Type.Bool
-import Data.Type.Equality
-import Data.Word
-import Foreign (Ptr)
-import GHC.TypeError
-import Montis.Core.KeyEvent
-import Montis.Core.W
-import Montis.Dsl.Input
-import Montis.Foreign.WlRoots (WlrInputDevice)
-
-data RecordedKey = RecordedKey Word32 Word32 KeyState Word32 Word32 Char
- deriving (Read, Show)
-
-data MacrosState = MacrosState
- { macros :: Map String [RecordedKey],
- currentlyRecording :: Maybe String
- }
- deriving (Read, Show)
-
-instance Default MacrosState where
- def = MacrosState mempty def
-
-instance ExtensionClass MacrosState
-
-type family Find a ls where
- Find b (a : t) = (b == a) || Find b t
- Find _ '[] = False
-
--- | Provides a Vim-esque keybinding behavior for macro recording.
---
--- Designed to be used like:
---
--- bind ev (Mod1 .+ 'q') macroStartStopKeybind
-macroStartStopKeybind :: (HasMacroSupport spy) => InputM spy ()
-macroStartStopKeybind = do
- currentlyRecordingMacro
- >>= ( \case
- Just ch -> do
- liftIO $ putStrLn $ "Done Recording: " ++ ch
- stopMacroRecording
- Nothing -> do
- (InputKeyEvent (KeyEvent {codepoint = cp})) <- nextInputPressEvent
- liftIO $ putStrLn $ "Recording: " ++ [cp]
- startRecording [cp]
- )
-
--- | Provides a keybinding for replaying a macro.
---
--- Designed to be used like:
---
--- bind ev (weak $ Mod1 .+ '@') macroReplayKeybind
-macroReplayKeybind :: (HasMacroSupport spy) => InputM spy ()
-macroReplayKeybind = do
- ( InputKeyEvent
- (KeyEvent {codepoint = cp, device = device})
- ) <-
- nextInputPressEvent
- replayMacro device [cp]
-
-startRecording :: (Wlike m) => String -> m ()
-startRecording ch =
- xmodify
- ( \m@MacrosState {macros = macros} ->
- m
- { macros = Map.delete ch macros,
- currentlyRecording = Just ch
- }
- )
-
-stopMacroRecording :: (Wlike m) => m ()
-stopMacroRecording = xmodify (\m -> m {currentlyRecording = Nothing})
-
-currentlyRecordingMacro :: (Wlike m) => m (Maybe String)
-currentlyRecordingMacro = xgets currentlyRecording
-
-replayMacro :: Ptr WlrInputDevice -> String -> InputM spy ()
-replayMacro inputDevice s = do
- m <- liftW (Map.lookup s <$> xgets macros)
- -- 'tail' is to cut off the last keystroke which stops the recording.
- mapM_ (replayEvents . map toInputEvent . reverse . tail) m
- where
- toInputEvent :: RecordedKey -> InputEvent
- toInputEvent (RecordedKey ts kc st mo keysym cp) =
- InputKeyEvent $ KeyEvent ts kc st mo keysym cp inputDevice
-
-pushMacroKey :: (Wlike m) => KeyEvent -> m ()
-pushMacroKey ke = do
- cur <- xgets currentlyRecording
- whenJust cur $ \ch -> do
- let recordedKey = toRecordedKey ke
- in xmodify $ \m@MacrosState {macros = macros} ->
- m {macros = Map.insertWith (++) ch [recordedKey] macros}
- where
- whenJust (Just a) fn = fn a
- whenJust _ _ = return ()
-
- toRecordedKey (KeyEvent ts c s m keysym cp _) = RecordedKey ts c s m keysym cp
-
--- | Phantom type defining a proxy required to support macros.
-data MacroSupport
-
--- | Instance for macro support.
-instance InputProxy MacroSupport where
- onKeyEvent _ ie = do
- lift $ whenKeyEvent ie pushMacroKey
- return ie
-
-class HasMacroSupport t
-
-instance
- ( If
- (Find MacroSupport t)
- True
- ( TypeError
- ( Text "This Requires the Macro Proxy to be Enabled."
- :<>: Text "Please enable this by adding MacroSupport to your"
- :<>: Text "inputProxies list.\n"
- :<>: Text "i.e. Change "
- :<>: ShowType t
- :<>: Text " to "
- :<>: ShowType (MacroSupport ': t)
- )
- )
- ~ True
- ) =>
- HasMacroSupport t
-
-instance HasMacroSupport MacroSupport
diff --git a/plug/src/Montis/Keys/MagicModifierKey.hs b/plug/src/Montis/Keys/MagicModifierKey.hs
deleted file mode 100644
index f9b87eb..0000000
--- a/plug/src/Montis/Keys/MagicModifierKey.hs
+++ /dev/null
@@ -1,51 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Keys.MagicModifierKey where
-
-import Control.Monad.RWS (MonadTrans (lift))
-import Control.Monad.Trans.Maybe (MaybeT (..))
-import Data.Data
-import Data.Default.Class
-import GHC.TypeNats
-import Montis.Core.KeyEvent
-import Montis.Core.W
-import Montis.Dsl.Input
-
-data MagicModifierProxy (keycode :: Natural) inputproxy
- deriving (Typeable)
-
-newtype MagicModifierState (keycode :: Natural) = MagicModifierState {isPressed :: Bool}
- deriving (Typeable, Eq, Show, Ord, Read)
-
-instance Default (MagicModifierState k) where
- def = MagicModifierState False
-
-instance (KnownNat k) => ExtensionClass (MagicModifierState k)
-
-instance
- (KnownNat keycode, InputProxy inputproxy) =>
- InputProxy (MagicModifierProxy keycode inputproxy)
- where
- onKeyEvent proxy ie = do
- case ie of
- (InputKeyEvent (KeyEvent {keycode = kc, state = state}))
- | fromIntegral kc == natVal (keycodeProxy proxy) -> do
- lift $ setMagicModifierPressed proxy (state == KeyPressed)
- MaybeT (return Nothing)
- _ -> do
- pressed <- lift $ isMagicModifierPressed proxy
- if pressed
- then onKeyEvent (Proxy :: Proxy inputproxy) ie
- else return ie
- where
- keycodeProxy :: Proxy (MagicModifierProxy kc a) -> Proxy kc
- keycodeProxy _ = Proxy
-
- isMagicModifierPressed p = isPressed <$> getModState p
- setMagicModifierPressed p = modifyModState p . const
-
- getModState :: (KnownNat kc) => Proxy (MagicModifierProxy kc a) -> W (MagicModifierState kc)
- getModState _ = xget
-
- modifyModState :: (KnownNat kc) => Proxy (MagicModifierProxy kc a) -> (MagicModifierState kc -> Bool) -> W ()
- modifyModState _ fn = xmodify (MagicModifierState . fn)
diff --git a/plug/src/Montis/Layout/Combine.hs b/plug/src/Montis/Layout/Combine.hs
deleted file mode 100644
index 8079da3..0000000
--- a/plug/src/Montis/Layout/Combine.hs
+++ /dev/null
@@ -1,49 +0,0 @@
-{-# LANGUAGE ViewPatterns #-}
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Layout.Combine where
-
-import Data.Typeable
-import Montis.Constraints
-import Montis.Core.W
-
-data (|||) a b = Comb LR a b
- deriving (Typeable, Read, Show)
-
-data Next = Next
- deriving (Typeable)
-
-data Reset = Reset
- deriving (Typeable)
-
-(|||) :: a -> b -> (a ||| b)
-a ||| b = Comb L a b
-
-data LR = L | R deriving (Read, Show, Ord, Eq, Enum)
-
-instance (HandleMessage a, HandleMessage b) => HandleMessage (a ||| b) where
- handleMessage (fromMessage -> Just Next) (Comb L l r) = return (Comb R l r)
- handleMessage (fromMessage -> Just Reset) (Comb _ l r) = return (Comb L l r)
- handleMessage mesg (Comb L l r) =
- Comb L <$> handleMessage mesg l <*> pure r
- handleMessage mesg (Comb R l r) =
- Comb L l <$> handleMessage mesg r
-
-instance (LayoutClass a, LayoutClass b) => LayoutClass (a ||| b) where
- -- In order to use this layout class, the lay-out type 'a' must satisfy BOTH
- -- the left and right constraints.
- type LayoutConstraint (a ||| b) = LayoutConstraint a &&&& LayoutConstraint b
-
- runLayout as (Comb R r l) = do
- (r', ret) <- runLayout as r
- return (Comb R r' l, ret)
- runLayout as (Comb L r l) = do
- (l', ret) <- runLayout as l
- return (Comb R r l', ret)
-
- serializeLayout (Comb lr l r) = show (Comb lr (serializeLayout l) (serializeLayout r))
- readLayout str = Comb lr <$> l <*> r
- where
- (Comb lr (readLayout -> l) (readLayout -> r)) = read str
-
- description (Comb _ l r) = description l ++ " ||| " ++ description r
diff --git a/plug/src/Montis/Layout/Full.hs b/plug/src/Montis/Layout/Full.hs
deleted file mode 100644
index 816ddc2..0000000
--- a/plug/src/Montis/Layout/Full.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-{-# OPTIONS_GHC -Wno-missing-export-lists #-}
-
-module Montis.Layout.Full where
-
-import Data.Data (Typeable)
-import Data.Default.Class
-import Montis.Constraints
-import Montis.Core.W
-import Montis.StackSet
-
-data Full = Full
- deriving (Read, Show, Typeable)
-
-instance Default Full where
- def = Full
-
-instance HandleMessage Full
-
-instance LayoutClass Full where
- type LayoutConstraint Full = Unconstrained
-
- runLayout = pureLayout $ \l _ ->
- case l of
- (focused -> Just a) -> [(a, RationalRect 1 1 1 1)]
- _ -> []
diff --git a/plug/src/Montis/StackSet.hs b/plug/src/Montis/StackSet.hs
deleted file mode 100644
index a147eb8..0000000
--- a/plug/src/Montis/StackSet.hs
+++ /dev/null
@@ -1,210 +0,0 @@
-{-# OPTIONS_GHC -Wno-orphans -Wno-missing-export-lists #-}
-
-module Montis.StackSet where
-
-import Control.Monad.Identity
-import Control.Monad.Writer (MonadWriter (tell), execWriter)
-import Data.Maybe (isJust, mapMaybe)
-import Data.Monoid (First (..))
-
--- | The root datastructure for holding the state of the windows.
-data StackSet s sd t l a = StackSet
- { -- | The currently selected screen.
- current :: Screen s sd t l a,
- -- | Remaining visible screens.
- visible :: [Screen s sd t l a],
- -- | Workspaces that exist, but are not on a screen.
- hidden :: [Workspace t l a]
- }
- deriving (Read, Show, Eq, Ord, Functor)
-
-class TraverseWorkspace f where
- traverseWorkspaces ::
- (Applicative m) => (Workspace t l a -> m (Workspace t' l' a')) -> f t l a -> m (f t' l' a')
-
-traverseWorkspaces_ :: (TraverseWorkspace f, Monad m) => (Workspace t l a -> m ()) -> f t l a -> m ()
-traverseWorkspaces_ f = void . traverseWorkspaces (\w -> f w >> pure w)
-
-foldMapWorkspaces ::
- (Monoid m, TraverseWorkspace f) => (Workspace t l a -> m) -> f t l a -> m
-foldMapWorkspaces fn = execWriter . traverseWorkspaces_ (tell . fn)
-
-mapWorkspaces ::
- (TraverseWorkspace f) =>
- (Workspace t l a -> Workspace t' l' a') ->
- f t l a ->
- f t' l' a'
-mapWorkspaces fn = runIdentity . traverseWorkspaces (pure . fn)
-
-instance TraverseWorkspace Workspace where
- traverseWorkspaces f = f
-
-instance TraverseWorkspace (Screen s sd) where
- traverseWorkspaces f scr = (\w' -> scr {workspace = w'}) <$> f (workspace scr)
-
-instance TraverseWorkspace (StackSet s sd) where
- traverseWorkspaces f (StackSet cur vis hid) =
- StackSet
- <$> traverseWorkspaces f cur
- <*> traverse (traverseWorkspaces f) vis
- <*> traverse (traverseWorkspaces f) hid
-
-instance Traversable Stack where
- traverse f (Stack u d) =
- Stack <$> traverse f u <*> traverse f d
-
-instance (TraverseWorkspace f) => Foldable (f t l) where
- foldMap fn =
- execWriter
- . traverseWorkspaces_ (\(Workspace _ _ s) -> tell (foldMap (fn . windowInSeat) s))
-
-instance (Functor (f t l), TraverseWorkspace f) => Traversable (f t l) where
- sequenceA =
- traverseWorkspaces $
- \(Workspace t l sf) -> Workspace t l <$> traverse sequenceA sf
-
-class HasFocus f where
- focused :: f a -> Maybe a
-
-data Rectangle = Rectangle Int Int Int Int
- deriving (Read, Show, Eq, Ord)
-
-instance HasFocus (StackSet s sd t l) where
- focused (StackSet c _ _) = focused c
-
-data Screen s sd t l a = Screen
- { screenDetail :: sd,
- screenId :: s,
- workspace :: Workspace t l a
- }
- deriving (Read, Show, Eq, Ord, Functor)
-
-instance HasFocus (Screen s sd t l) where
- focused (Screen _ _ w) = focused w
-
--- | Defines where a window should appear.
-data WindowSeat a = Floating Rectangle a | Tiled a
- deriving (Read, Show, Eq, Ord, Functor, Foldable)
-
-windowInSeat :: WindowSeat a -> a
-windowInSeat (Floating _ a) = a
-windowInSeat (Tiled a) = a
-
-instance Traversable WindowSeat where
- sequenceA (Floating r fa) = Floating r <$> fa
- sequenceA (Tiled fa) = Tiled <$> fa
-
-instance HasFocus WindowSeat where
- focused (Floating _ a) = Just a
- focused (Tiled a) = Just a
-
-data Workspace t l a = Workspace
- { tag :: t,
- layout :: l,
- stack :: Stack (WindowSeat a)
- }
- deriving (Read, Show, Eq, Ord, Functor)
-
-instance HasFocus (Workspace t l) where
- focused (Workspace _ _ s) = windowInSeat <$> focused s
-
-data Stack a = Stack
- { -- | The elements above the focused one.
- up :: ![a],
- -- | The elements below the focused one including the focused one itself.
- down :: ![a]
- }
- deriving (Read, Show, Eq, Ord, Functor, Foldable)
-
-instance HasFocus Stack where
- focused (Stack _ (a : _)) = Just a
- focused _ = Nothing
-
--- | Change the tag in a structure.
-mapTag :: (TraverseWorkspace f) => (t -> t') -> f t l a -> f t' l a
-mapTag fn = mapWorkspaces (\w -> w {tag = fn (tag w)})
-
--- | Change the layout in a structure.
-mapLayout :: (TraverseWorkspace f) => (l -> l') -> f t l a -> f t l' a
-mapLayout fn = mapWorkspaces (\w -> w {layout = fn (layout w)})
-
--- | Return all the tags in a structure.
-tags :: (TraverseWorkspace f) => f t l a -> [t]
-tags = foldMapWorkspaces ((: []) . tag)
-
--- | Insert a new window into the StackSet. The optional rectangle indicates if
--- the window should be floating or tiled.
---
--- The window is inserted just above the the currently focused window and is
--- given focus.
-insert :: a -> Maybe Rectangle -> StackSet s sd t l a -> StackSet s sd t l a
-insert win rect =
- runIdentity
- . onCurrentStack
- ( \(Stack u d) ->
- return $
- (\w -> Stack u (w : d)) $
- maybe (Tiled win) (`Floating` win) rect
- )
-
--- | Find the tag associated with a window.
-findTag :: (TraverseWorkspace f, Eq a) => a -> f t l a -> Maybe t
-findTag a =
- getFirst
- . foldMapWorkspaces
- ( \ws ->
- foldMap
- ( \a' ->
- First $ if a' == a then Just (tag ws) else Nothing
- )
- ws
- )
-
--- | Return true if the window exist in a structure
-elem :: (TraverseWorkspace f, Eq a) => a -> f t l a -> Bool
-elem a = isJust . findTag a
-
--- | Convenience function for inserting a window in stack set tiled.
-insertTiled :: a -> StackSet s sd t l a -> StackSet s sd t l a
-insertTiled win = insert win Nothing
-
-integrate :: Stack a -> [a]
-integrate (Stack u d) = u ++ d
-
-differentiate :: [a] -> Stack a
-differentiate = Stack []
-
-applyStack ::
- (Monad m) =>
- (Stack (WindowSeat a) -> m (Stack (WindowSeat a))) ->
- Workspace t l a ->
- m (Workspace t l a)
-applyStack fn (Workspace t l s) = Workspace t l <$> fn s
-
--- | Apply a function to the currently focused stack.
-onCurrentStack ::
- (Monad m) =>
- (Stack (WindowSeat a) -> m (Stack (WindowSeat a))) ->
- StackSet s sd t l a ->
- m (StackSet s sd t l a)
-onCurrentStack fn (StackSet cur vis hid) =
- StackSet <$> cur' cur <*> pure vis <*> pure hid
- where
- cur' (Screen s sd ws) = Screen s sd <$> ws' ws
- ws' (Workspace t l s) = Workspace t l <$> fn s
-
-catMaybes :: StackSet s sd t l (Maybe a) -> StackSet s sd t l a
-catMaybes (StackSet cur hidden visible) =
- StackSet (catMaybesS cur) (map catMaybesS hidden) (map catMaybesW visible)
- where
- catMaybesS (Screen a b ws) = Screen a b $ catMaybesW ws
- catMaybesW (Workspace a b st) = Workspace a b $ catMaybesSt st
- catMaybesSt (Stack up down) =
- Stack (mapMaybe sequenceA up) (mapMaybe sequenceA down)
-
-filter :: (a -> Bool) -> StackSet s sd t l a -> StackSet s sd t l a
-filter ffn =
- Montis.StackSet.catMaybes . fmap (\a -> if ffn a then Just a else Nothing)
-
-delete :: (Eq a) => a -> StackSet s sd t l a -> StackSet s sd t l a
-delete win = Montis.StackSet.filter (/= win)