aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-02-28 12:37:51 -0700
committerJosh Rahm <joshuarahm@gmail.com>2024-02-28 12:40:50 -0700
commite7300f03dcf0af7d968977000a10e8a8befdb60a (patch)
tree8f853663851a27b8914e429eda45b0c1fb97dd0b
parentb444f874bc12cb8710068200500f14fd1e5f6776 (diff)
downloadwetterhorn-main.tar.gz
wetterhorn-main.tar.bz2
wetterhorn-main.zip
Huge refactor for the Haskell code.HEADmain
This adds new layout configuration, preparing for actually using the layouts. This also restructures the code and tries to keep code interfacing with the foreign structures together and rename them to more sensible names.
-rw-r--r--package.yaml12
-rw-r--r--src/Config.hs16
-rw-r--r--src/Wetterhorn/Constraints.hs12
-rw-r--r--src/Wetterhorn/Core.hs318
-rw-r--r--src/Wetterhorn/Core/KeyEvent.hs22
-rw-r--r--src/Wetterhorn/Core/SurfaceEvent.hs16
-rw-r--r--src/Wetterhorn/Core/W.hs151
-rw-r--r--src/Wetterhorn/Foreign.hs18
-rw-r--r--src/Wetterhorn/Foreign/Export.hs (renamed from src/Wetterhorn/FFI.hs)55
-rw-r--r--src/Wetterhorn/Foreign/ForeignInterface.hs (renamed from src/Wetterhorn/Core/ForeignInterface.hs)10
-rw-r--r--src/Wetterhorn/Foreign/WlRoots.hs (renamed from src/Wetterhorn/WlRoots.hs)2
-rw-r--r--src/Wetterhorn/Layout/Combine.hs45
-rw-r--r--src/Wetterhorn/Layout/Full.hs18
-rw-r--r--src/Wetterhorn/StackSet.hs185
14 files changed, 674 insertions, 206 deletions
diff --git a/package.yaml b/package.yaml
index ebc64c1..fee7560 100644
--- a/package.yaml
+++ b/package.yaml
@@ -33,6 +33,8 @@ dependencies:
- mtl
- bytestring
- containers
+- data-default
+
ghc-options:
- -Wall
@@ -44,10 +46,14 @@ ghc-options:
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
-- -XTupleSections
+- -XGHC2021
+- -XTypeFamilies
+- -XUndecidableInstances
+- -XGADTs
+- -XFunctionalDependencies
+- -XUndecidableSuperClasses
+- -XDefaultSignatures
- -XViewPatterns
-- -XGeneralizedNewtypeDeriving
-- -XRankNTypes
- -fPIC
executables:
diff --git a/src/Config.hs b/src/Config.hs
index 818150e..e49a869 100644
--- a/src/Config.hs
+++ b/src/Config.hs
@@ -1,6 +1,14 @@
-module Config (wetterhorn) where
+module Config where
-import Wetterhorn.Core
+import Wetterhorn.Core.W
+import Wetterhorn.Layout.Full
+import Wetterhorn.Layout.Combine
-wetterhorn :: IO Wetterhorn
-wetterhorn = initWetterhorn defaultConfig
+config = defaultConfig {
+ keyHook = wio . print,
+ surfaceHook = wio . print,
+ layout = WindowLayout Full
+}
+
+-- wetterhorn :: IO Wetterhorn
+-- wetterhorn = initWetterhorn defaultConfig
diff --git a/src/Wetterhorn/Constraints.hs b/src/Wetterhorn/Constraints.hs
new file mode 100644
index 0000000..cdc5afe
--- /dev/null
+++ b/src/Wetterhorn/Constraints.hs
@@ -0,0 +1,12 @@
+-- | Contains useful constraints and constraint combinators.
+module Wetterhorn.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/src/Wetterhorn/Core.hs b/src/Wetterhorn/Core.hs
index d3515fc..d853191 100644
--- a/src/Wetterhorn/Core.hs
+++ b/src/Wetterhorn/Core.hs
@@ -1,176 +1,152 @@
{-# HLINT ignore "Use camelCase" #-}
module Wetterhorn.Core
- ( WState (..),
- WConfig (..),
- SurfaceState (..),
- W,
- getWConfig,
- getWState,
- runW,
- Wetterhorn,
- initWetterhorn,
- wio,
- incrementState,
- readWState,
- defaultConfig,
- requestHotReload,
- ctxConfig,
- KeyEvent (..),
- KeyState (..),
- )
+-- ( WState (..),
+-- WConfig (..),
+-- SurfaceState (..),
+-- W,
+-- getWConfig,
+-- getWState,
+-- runW,
+-- Wetterhorn,
+-- initWetterhorn,
+-- wio,
+-- incrementState,
+-- readWState,
+-- defaultConfig,
+-- requestHotReload,
+-- ctxConfig,
+-- KeyEvent (..),
+-- KeyState (..),
+-- )
where
-import Control.Arrow (first)
-import Control.Exception
-import Control.Monad (when)
-import Data.ByteString (ByteString)
-import qualified Data.ByteString.Char8 as CH
-import Data.Char (chr, ord)
-import Data.Map (Map)
-import qualified Data.Map as Map
-import Data.Maybe (fromMaybe)
-import Foreign (Ptr, StablePtr, Word32, castForeignPtr, newStablePtr, ptrToIntPtr)
-import Numeric (showHex)
-import Text.Printf
-import Wetterhorn.Core.ForeignInterface (ForeignInterface)
-import qualified Wetterhorn.Core.ForeignInterface as ForeignInterface
-import Wetterhorn.WlRoots
-
-data WContext = WContext
- { ctxForeignInterface :: ForeignInterface,
- ctxConfig :: WConfig
- }
-
--- This is the OpaqueState passed to the harness.
-type Wetterhorn = StablePtr (WContext, WState)
-
-requestHotReload :: W ()
-requestHotReload = do
- fi <- ctxForeignInterface <$> getWContext
- wio $ ForeignInterface.requestHotReload fi
-
-requestLog :: String -> W ()
-requestLog str = do
- fi <- ctxForeignInterface <$> getWContext
- wio $ ForeignInterface.requestLog fi str
-
-requestExit :: Int -> W ()
-requestExit ec = do
- fi <- ctxForeignInterface <$> getWContext
- wio $ ForeignInterface.requestExit fi ec
-
-initWetterhorn :: WConfig -> IO Wetterhorn
-initWetterhorn conf = do
- foreignInterface <- ForeignInterface.getForeignInterface
- newStablePtr (WContext foreignInterface conf, WState "this is a string" 0)
-
-data WState = WState
- { someString :: String,
- integer :: Int
- }
- deriving (Show, Read)
-
-data SurfaceState = Map | Unmap | Destroy deriving (Eq, Ord, Show, Enum)
-
-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)
-
-data WConfig = WConfig
- { keybindingHandler :: KeyEvent -> W Bool,
- surfaceHandler :: SurfaceState -> Surface -> W ()
- }
-
-defaultBindings :: Map (KeyState, Word32, Word32) (W ())
-defaultBindings =
- Map.fromList
- [ ((KeyPressed, 0x9, sym 'Q'), requestHotReload),
- ((KeyPressed, 0x8, sym 'r'), wio $ ForeignInterface.doShellExec "wofi --show run"),
- ((KeyPressed, 0x8, sym 'l'), requestLog "This is a log statement!\n"),
- ((KeyPressed, 0x8, sym 't'), wio $ ForeignInterface.doShellExec "alacritty"),
- ((KeyPressed, 0x9, sym 'T'), wio $ ForeignInterface.doShellExec "gnome-terminal"),
- ((KeyPressed, 0x8, sym 'c'), wio $ ForeignInterface.doShellExec "pavucontrol"),
- ((KeyPressed, 0x8, sym 'q'), requestExit 0)
- ]
- where
- sym = fromIntegral . ord
-
-defaultConfig :: WConfig
-defaultConfig =
- WConfig
- { keybindingHandler = \keyEvent -> do
- seatPtr <- (wio . ForeignInterface.getSeat . ctxForeignInterface) =<< getWContext
-
- maybe
- ( wio $ do
- wlrSeatSetKeyboard seatPtr (device keyEvent)
- wlrSeatKeyboardNotifyKey
- seatPtr
- (timeMs keyEvent)
- (keycode keyEvent)
- ( case state keyEvent of
- KeyReleased -> 0
- _ -> 1
- )
-
- return True
- )
- (fmap (const True))
- $ Map.lookup
- (state keyEvent, modifiers keyEvent, keysym keyEvent)
- defaultBindings,
- surfaceHandler = \state surface -> wio (printf "Surface %s is %s\n" (show surface) (show state))
- }
-
-readWState :: ByteString -> IO WState
-readWState bs =
- catch
- (return $ read (CH.unpack bs))
- ( \e ->
- let _ = (e :: SomeException) in return (WState "" 0)
- )
-
-newtype W a = W ((WContext, WState) -> IO (a, WState))
-
-instance Functor W where
- fmap mfn (W fn) = W $ fmap (first mfn) <$> fn
-
-instance Applicative W where
- pure a = W $ \(_, s) -> return (a, s)
- mfn <*> ma = do
- fn <- mfn
- fn <$> ma
-
-instance Monad W where
- (W fntoa) >>= fnmb = W $ \(config, state) -> do
- (a, state') <- fntoa (config, state)
- let W fntob = fnmb a
- fntob (config, state')
-
-getWContext :: W WContext
-getWContext = W pure
-
-getWConfig :: W WConfig
-getWConfig = ctxConfig <$> getWContext
-
-getWState :: W WState
-getWState = W $ \(_, s) -> pure (s, s)
-
-runW :: W a -> (WContext, WState) -> IO (a, WState)
-runW (W fn) = fn
-
-incrementState :: W Int
-incrementState = W $ \(_, WState s i) -> return (i, WState s (i + 1))
-
-wio :: IO a -> W a
-wio fn = W $ \(_, b) -> fn >>= \a -> return (a, b)
+-- import Control.Arrow (first)
+-- import Control.Exception
+-- import Data.ByteString (ByteString)
+-- import Data.Char (ord)
+-- import Data.Map (Map)
+-- import Foreign (Ptr, StablePtr, Word32, newStablePtr)
+-- import Text.Printf
+-- import Wetterhorn.Foreign.ForeignInterface (ForeignInterface)
+-- import Wetterhorn.Foreign.WlRoots
+-- import qualified Data.ByteString.Char8 as CH
+-- import qualified Data.Map as Map
+-- import qualified Wetterhorn.Foreign.ForeignInterface as ForeignInterface
+--
+-- data WContext = WContext
+-- { ctxForeignInterface :: ForeignInterface,
+-- ctxConfig :: WConfig
+-- }
+--
+-- -- This is the OpaqueState passed to the harness.
+-- type Wetterhorn = StablePtr (WContext, WState)
+--
+-- requestHotReload :: W ()
+-- requestHotReload = do
+-- fi <- ctxForeignInterface <$> getWContext
+-- wio $ ForeignInterface.requestHotReload fi
+--
+-- requestLog :: String -> W ()
+-- requestLog str = do
+-- fi <- ctxForeignInterface <$> getWContext
+-- wio $ ForeignInterface.requestLog fi str
+--
+-- requestExit :: Int -> W ()
+-- requestExit ec = do
+-- fi <- ctxForeignInterface <$> getWContext
+-- wio $ ForeignInterface.requestExit fi ec
+--
+-- initWetterhorn :: WConfig -> IO Wetterhorn
+-- initWetterhorn conf = do
+-- foreignInterface <- ForeignInterface.getForeignInterface
+-- newStablePtr (WContext foreignInterface conf, WState "this is a string" 0)
+--
+-- defaultBindings :: Map (KeyState, Word32, Word32) (W ())
+-- defaultBindings =
+-- Map.fromList
+-- [ ((KeyPressed, 0x9, sym 'Q'), requestHotReload),
+-- ((KeyPressed, 0x8, sym 'r'), wio $ ForeignInterface.doShellExec "wofi --show run"),
+-- ((KeyPressed, 0x8, sym 'l'), requestLog "This is a log statement!\n"),
+-- ((KeyPressed, 0x8, sym 't'), wio $ ForeignInterface.doShellExec "alacritty"),
+-- ((KeyPressed, 0x9, sym 'T'), wio $ ForeignInterface.doShellExec "gnome-terminal"),
+-- ((KeyPressed, 0x8, sym 'c'), wio $ ForeignInterface.doShellExec "pavucontrol"),
+-- ( (KeyPressed, 0x8, sym 'p'),
+-- wio $ do
+-- putStrLn "Maps:"
+-- putStrLn =<< readFile "/proc/self/maps"
+-- ),
+-- ((KeyPressed, 0x8, sym 'q'), requestExit 0)
+-- ]
+-- where
+-- sym = fromIntegral . ord
+--
+-- defaultConfig :: WConfig
+-- defaultConfig =
+-- WConfig
+-- { keybindingHandler = \keyEvent -> do
+-- seatPtr <- (wio . ForeignInterface.getSeat . ctxForeignInterface) =<< getWContext
+--
+-- maybe
+-- ( wio $ do
+-- wlrSeatSetKeyboard seatPtr (device keyEvent)
+-- wlrSeatKeyboardNotifyKey
+-- seatPtr
+-- (timeMs keyEvent)
+-- (keycode keyEvent)
+-- ( case state keyEvent of
+-- KeyReleased -> 0
+-- _ -> 1
+-- )
+--
+-- return True
+-- )
+-- (fmap (const True))
+-- $ Map.lookup
+-- (state keyEvent, modifiers keyEvent, keysym keyEvent)
+-- defaultBindings,
+-- surfaceHandler = \state surface -> wio (printf "Surface %s is %s\n" (show surface) (show state))
+-- }
+--
+-- readWState :: ByteString -> IO WState
+-- readWState bs =
+-- catch
+-- (return $ read (CH.unpack bs))
+-- ( \e ->
+-- let _ = (e :: SomeException) in return (WState "" 0)
+-- )
+--
+-- newtype W a = W ((WContext, WState) -> IO (a, WState))
+--
+-- instance Functor W where
+-- fmap mfn (W fn) = W $ fmap (first mfn) <$> fn
+--
+-- instance Applicative W where
+-- pure a = W $ \(_, s) -> return (a, s)
+-- mfn <*> ma = do
+-- fn <- mfn
+-- fn <$> ma
+--
+-- instance Monad W where
+-- (W fntoa) >>= fnmb = W $ \(config, state) -> do
+-- (a, state') <- fntoa (config, state)
+-- let W fntob = fnmb a
+-- fntob (config, state')
+--
+-- getWContext :: W WContext
+-- getWContext = W pure
+--
+-- getWConfig :: W WConfig
+-- getWConfig = ctxConfig <$> getWContext
+--
+-- getWState :: W WState
+-- getWState = W $ \(_, s) -> pure (s, s)
+--
+-- runW :: W a -> (WContext, WState) -> IO (a, WState)
+-- runW (W fn) = fn
+--
+-- incrementState :: W Int
+-- incrementState = W $ \(_, WState s i) -> return (i, WState s (i + 1))
+--
+-- wio :: IO a -> W a
+-- wio fn = W $ \(_, b) -> fn >>= \a -> return (a, b)
diff --git a/src/Wetterhorn/Core/KeyEvent.hs b/src/Wetterhorn/Core/KeyEvent.hs
new file mode 100644
index 0000000..77d273f
--- /dev/null
+++ b/src/Wetterhorn/Core/KeyEvent.hs
@@ -0,0 +1,22 @@
+module Wetterhorn.Core.KeyEvent
+ ( KeyEvent (..),
+ KeyState (..),
+ )
+where
+
+import Data.Word (Word32)
+import Foreign (Ptr)
+import Wetterhorn.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/src/Wetterhorn/Core/SurfaceEvent.hs b/src/Wetterhorn/Core/SurfaceEvent.hs
new file mode 100644
index 0000000..3e7eaf3
--- /dev/null
+++ b/src/Wetterhorn/Core/SurfaceEvent.hs
@@ -0,0 +1,16 @@
+module Wetterhorn.Core.SurfaceEvent
+ ( SurfaceEvent (..),
+ SurfaceState (..),
+ )
+where
+
+import Wetterhorn.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/src/Wetterhorn/Core/W.hs b/src/Wetterhorn/Core/W.hs
new file mode 100644
index 0000000..89ebf4b
--- /dev/null
+++ b/src/Wetterhorn/Core/W.hs
@@ -0,0 +1,151 @@
+module Wetterhorn.Core.W where
+
+import Control.Arrow (Arrow (first))
+import Control.Monad.RWS (MonadIO (liftIO), MonadReader, MonadState)
+import Control.Monad.Reader (ReaderT (runReaderT))
+import Control.Monad.State (StateT (runStateT))
+import Data.Data (Typeable, cast)
+import Data.Kind (Constraint, Type)
+import Data.Set (Set)
+import Foreign (StablePtr)
+import Text.Read
+import Wetterhorn.Core.KeyEvent
+import Wetterhorn.Core.SurfaceEvent
+import Wetterhorn.Foreign
+import Wetterhorn.Foreign.ForeignInterface (ForeignInterface)
+import Wetterhorn.StackSet hiding (layout)
+
+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
+
+-- | Types of this class "lay out" windows by assigning rectangles and handle
+-- messages.
+class (Typeable 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 C l :: Type -> Constraint
+
+ -- | Executes the layout on some windows in a pure way. Returns a list of
+ -- windows to their assigned rectangle.
+ pureLayout :: (C l a) => [a] -> l -> [(a, RationalRect)]
+ pureLayout as _ = map (,RationalRect 0 0 0 0) as
+
+ -- | Runs the layout in an impure way returning a modified layout and the list
+ -- of windows to their rectangles under a monad.
+ runLayout :: (C l a) => [a] -> l -> W (l, [(a, RationalRect)])
+ runLayout as l = return (l, pureLayout as l)
+
+ -- | Handles a message in a pure way. Returns the new layout after handling
+ -- the message.
+ pureMessage :: Message -> l -> l
+ pureMessage _ = id
+
+ -- | Handles a message in an impure way.
+ handleMessage :: Message -> l -> W l
+ handleMessage m = return . pureMessage m
+
+ 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
+
+-- 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, C l a, a ~ Window) => WindowLayout l
+
+runWindowLayout :: [Window] -> WindowLayout -> W (WindowLayout, [(Window, RationalRect)])
+runWindowLayout as (WindowLayout l) = first WindowLayout <$> runLayout as l
+
+handleWindowMessage :: Message -> WindowLayout -> 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
+
+serializeWindowLayout :: WindowLayout -> String
+serializeWindowLayout (WindowLayout l) = serializeLayout l
+
+type ScreenId = ()
+
+type ScreenDetail = ()
+
+type Tag = String
+
+newtype Window = Window (TypedIntPtr ())
+ deriving (Eq, Ord, Show, Read)
+
+type Wetterhorn = StablePtr (Context, State)
+
+data Context = Context
+ { ctxForeignInterface :: ForeignInterface,
+ ctxConfig :: Config WindowLayout
+ }
+
+defaultConfig :: Config ()
+defaultConfig =
+ Config
+ { keyHook = \_ -> return (),
+ surfaceHook = \_ -> return (),
+ layout = ()
+ }
+
+data Config l = Config
+ { keyHook :: KeyEvent -> W (),
+ surfaceHook :: SurfaceEvent -> W (),
+ layout :: l
+ }
+
+data State = State
+ { mapped :: StackSet ScreenId ScreenDetail Tag WindowLayout Window,
+ allWindows :: Set Window
+ }
+
+initColdState :: WindowLayout -> IO State
+initColdState l = return $ State (StackSet (Screen () () (Workspace "0" l (Stack [] []))) [] []) mempty
+
+marshalState :: State -> String
+marshalState (State mapped allWindows) =
+ show
+ ( mapLayout serializeWindowLayout mapped,
+ allWindows
+ )
+
+demarshalState :: WindowLayout -> String -> State
+demarshalState witness str = State mapped allWindows
+ where
+ (mapLayout (readWindowLayout witness) -> mapped, allWindows) = read str
+
+newtype W a = W (ReaderT Context (StateT State IO) a)
+ deriving (Functor, Applicative, Monad, MonadState State, MonadReader Context, MonadIO)
+
+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
diff --git a/src/Wetterhorn/Foreign.hs b/src/Wetterhorn/Foreign.hs
new file mode 100644
index 0000000..2d0a42c
--- /dev/null
+++ b/src/Wetterhorn/Foreign.hs
@@ -0,0 +1,18 @@
+module Wetterhorn.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/src/Wetterhorn/FFI.hs b/src/Wetterhorn/Foreign/Export.hs
index 6173291..0d71a4e 100644
--- a/src/Wetterhorn/FFI.hs
+++ b/src/Wetterhorn/Foreign/Export.hs
@@ -1,6 +1,6 @@
-- | This module does not export anything. It exists simply to provide C-symbols
-- for the plugin.
-module Wetterhorn.FFI () where
+module Wetterhorn.Foreign.Export () where
import Config
import Control.Monad (forM_)
@@ -17,30 +17,33 @@ import Foreign
newStablePtr,
)
import Foreign.C (CChar, CInt (..))
-import Wetterhorn.Core
-import Wetterhorn.Core.ForeignInterface
-import Wetterhorn.WlRoots
-
-runForeign :: (WConfig -> W ()) -> Wetterhorn -> IO Wetterhorn
+import Wetterhorn.Core.KeyEvent (KeyEvent (..), KeyState (..))
+import Wetterhorn.Core.SurfaceEvent (SurfaceEvent (SurfaceEvent))
+import Wetterhorn.Core.W (W, Wetterhorn)
+import qualified Wetterhorn.Core.W as W
+import Wetterhorn.Foreign.ForeignInterface
+import Wetterhorn.Foreign.WlRoots
+
+runForeign :: (forall l. W.Config l -> W ()) -> Wetterhorn -> IO Wetterhorn
runForeign fn stblptr = do
(ctx, st) <- deRefStablePtr stblptr
freeStablePtr stblptr
- (_, state') <- runW (fn $ ctxConfig ctx) (ctx, st)
+ (_, state') <- W.runW (fn $ W.ctxConfig ctx) (ctx, st)
newStablePtr (ctx, state')
-runForeignWithReturn :: (Storable a) => (WConfig -> W a) -> Ptr a -> Wetterhorn -> IO Wetterhorn
+runForeignWithReturn :: (Storable a) => (forall l. W.Config l -> W a) -> Ptr a -> Wetterhorn -> IO Wetterhorn
runForeignWithReturn fn ptr stableptr = do
(ctx, st) <- deRefStablePtr stableptr
freeStablePtr stableptr
- (val, state') <- runW (fn $ ctxConfig ctx) (ctx, st)
+ (val, state') <- W.runW (fn $ W.ctxConfig ctx) (ctx, st)
poke ptr val
newStablePtr (ctx, state')
-runForeignWithReturn2 :: (Storable a, Storable b) => (WConfig -> W (a, b)) -> Ptr a -> Ptr b -> Wetterhorn -> IO Wetterhorn
+runForeignWithReturn2 :: (Storable a, Storable b) => (forall l. W.Config l -> W (a, b)) -> Ptr a -> Ptr b -> Wetterhorn -> IO Wetterhorn
runForeignWithReturn2 fn ptrA ptrB stableptr = do
(ctx, st) <- deRefStablePtr stableptr
freeStablePtr stableptr
- ((vA, vB), state') <- runW (fn $ ctxConfig ctx) (ctx, st)
+ ((vA, vB), state') <- W.runW (fn $ W.ctxConfig ctx) (ctx, st)
poke ptrA vA
poke ptrB vB
newStablePtr (ctx, state')
@@ -55,10 +58,11 @@ foreign export ccall "plugin_hot_start"
pluginHotStart :: Ptr CChar -> Word32 -> IO Wetterhorn
pluginHotStart chars len = do
bs <- BS.packCStringLen (chars, fromIntegral len)
- wtrPtr <- wetterhorn
- (conf, _) <- deRefStablePtr wtrPtr
- freeStablePtr wtrPtr
- newStablePtr . (conf,) =<< readWState bs
+ foreignInterface <- getForeignInterface
+ newStablePtr
+ ( W.Context foreignInterface config,
+ W.demarshalState (W.layout config) (CH.unpack bs)
+ )
-- | This function is called when a "coldstart" request is receieved. It just
-- calles the function "wetterhorn". This function should be defined in the main
@@ -67,7 +71,10 @@ foreign export ccall "plugin_cold_start"
pluginColdStart :: IO Wetterhorn
pluginColdStart :: IO Wetterhorn
-pluginColdStart = wetterhorn
+pluginColdStart = do
+ foreignInterface <- getForeignInterface
+ state <- W.initColdState (W.layout config)
+ newStablePtr (W.Context foreignInterface config, state)
-- | Marshals the opaque state to a C-style byte array and size pointer.
foreign export ccall "plugin_marshal_state"
@@ -76,7 +83,7 @@ foreign export ccall "plugin_marshal_state"
pluginMarshalState :: Wetterhorn -> Ptr Word32 -> IO (Ptr Word8)
pluginMarshalState stblptr outlen = do
(_, st) <- deRefStablePtr stblptr
- let bs = CH.pack (show st)
+ 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
@@ -105,7 +112,7 @@ pluginHandleKeybinding ::
IO Wetterhorn
pluginHandleKeybinding inputDevicePtr eventPtr mods sym cp =
runForeignWithReturn $ \config -> do
- event <- wio $
+ event <- W.wio $
runForeignDemarshal eventPtr $ do
tMs <- demarshal
kc <- demarshal
@@ -120,7 +127,8 @@ pluginHandleKeybinding inputDevicePtr eventPtr mods sym cp =
sym
(toEnum $ fromIntegral cp)
inputDevicePtr
- (\b -> if b then 1 else 0) <$> keybindingHandler config event
+ W.keyHook config event
+ return 1
-- | Function exported to the harness to handle the mapping/unmapping/deletion
-- of an XDG surface.
@@ -130,7 +138,12 @@ foreign export ccall "plugin_handle_surface"
pluginHandleSurface :: Ptr WlrXdgSurface -> CInt -> Wetterhorn -> IO Wetterhorn
pluginHandleSurface p t =
- runForeign (\c -> surfaceHandler c (toEnum $ fromIntegral t) (toSurface p))
+ runForeign
+ ( \c ->
+ W.surfaceHook
+ c
+ $ SurfaceEvent (toEnum $ fromIntegral t) (toSurface p)
+ )
-- | Function exported to the harness to handle the mapping/unmapping/deletion
-- of an XWayland surface.
@@ -140,4 +153,4 @@ foreign export ccall "plugin_handle_xwayland_surface"
pluginHandleXWaylandSurface :: Ptr WlrXWaylandSurface -> CInt -> Wetterhorn -> IO Wetterhorn
pluginHandleXWaylandSurface p t =
- runForeign (\c -> surfaceHandler c (toEnum $ fromIntegral t) (toSurface p))
+ runForeign (\c -> W.surfaceHook c $ SurfaceEvent (toEnum $ fromIntegral t) (toSurface p))
diff --git a/src/Wetterhorn/Core/ForeignInterface.hs b/src/Wetterhorn/Foreign/ForeignInterface.hs
index 5dc1454..471e3a9 100644
--- a/src/Wetterhorn/Core/ForeignInterface.hs
+++ b/src/Wetterhorn/Foreign/ForeignInterface.hs
@@ -1,22 +1,20 @@
-module Wetterhorn.Core.ForeignInterface
+module Wetterhorn.Foreign.ForeignInterface
( getForeignInterface,
ForeignInterface (..),
ForeignDemarshal (..),
runForeignDemarshal,
demarshal,
- doShellExec
+ doShellExec,
)
where
import Control.Monad.State (MonadState (get, put), MonadTrans (lift), StateT, evalStateT)
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Char8 as BC
import Data.Void (Void)
-import Foreign (Ptr, Storable (peek, sizeOf), Word8, castPtr, plusPtr)
+import Foreign (Ptr, Storable (peek, sizeOf), castPtr, plusPtr)
import Foreign.C (CChar, CInt (..))
import Foreign.C.String
import GHC.Exts (FunPtr)
-import Wetterhorn.WlRoots
+import Wetterhorn.Foreign.WlRoots
newtype ForeignDemarshal a = ForeignDemarshal (StateT (Ptr ()) IO a)
deriving (Functor, Monad, Applicative, MonadState (Ptr ()))
diff --git a/src/Wetterhorn/WlRoots.hs b/src/Wetterhorn/Foreign/WlRoots.hs
index 7a2a237..56f2a2c 100644
--- a/src/Wetterhorn/WlRoots.hs
+++ b/src/Wetterhorn/Foreign/WlRoots.hs
@@ -1,4 +1,4 @@
-module Wetterhorn.WlRoots where
+module Wetterhorn.Foreign.WlRoots where
import Foreign (Ptr, Word32)
diff --git a/src/Wetterhorn/Layout/Combine.hs b/src/Wetterhorn/Layout/Combine.hs
new file mode 100644
index 0000000..983ceb1
--- /dev/null
+++ b/src/Wetterhorn/Layout/Combine.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE ViewPatterns #-}
+
+module Wetterhorn.Layout.Combine where
+
+import Data.Typeable
+import Wetterhorn.Constraints
+import Wetterhorn.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 (LayoutClass a, LayoutClass b) => LayoutClass (a ||| b) where
+ type C (a ||| b) = C a &&&& C 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)
+
+ 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
+
+ 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/src/Wetterhorn/Layout/Full.hs b/src/Wetterhorn/Layout/Full.hs
new file mode 100644
index 0000000..8296c7b
--- /dev/null
+++ b/src/Wetterhorn/Layout/Full.hs
@@ -0,0 +1,18 @@
+module Wetterhorn.Layout.Full where
+
+import Data.Data (Typeable)
+import Data.Default
+import Wetterhorn.Constraints
+import Wetterhorn.Core.W
+
+data Full = Full
+ deriving (Read, Show, Typeable)
+
+instance Default Full where
+ def = Full
+
+instance LayoutClass Full where
+ type C Full = Unconstrained
+
+ pureLayout (a : _) _ = [(a, RationalRect 1 1 1 1)]
+ pureLayout _ _ = []
diff --git a/src/Wetterhorn/StackSet.hs b/src/Wetterhorn/StackSet.hs
new file mode 100644
index 0000000..464fd54
--- /dev/null
+++ b/src/Wetterhorn/StackSet.hs
@@ -0,0 +1,185 @@
+module Wetterhorn.StackSet where
+
+import Control.Monad.Identity
+import Control.Monad.Writer (First (..), MonadWriter (tell), execWriter)
+import Data.Maybe (isJust)
+
+-- | 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)
+
+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
+
+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