diff --git a/.envrc b/.envrc index be81fed..1d953f4 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -eval "$(lorri direnv)" \ No newline at end of file +use nix diff --git a/shell.nix b/shell.nix index e83e6dd..b3174a2 100644 --- a/shell.nix +++ b/shell.nix @@ -2,13 +2,13 @@ let pkgs = import {}; dhall = import (fetchTarball { url = https://hydra.dhall-lang.org/jobset/dhall-haskell/master/channel/latest/nixexprs.tar.bz2; - sha256 = "127r3nabcc4snqy1jap0yyas2cfjyiz5qxhz5dyjd56nznkfda5a"; + sha256 = "0636c1lz1ndsdskgvqcbrfx2bav6hycdwlbsbyv0lvl9ykca1mk4"; }) {}; nix-tools = import (fetchTarball { url = https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz; sha256 = "08sf4k9w6gn2scp86bcq0j27akjrxykf8dziy12dm61aqhwy948q"; }) {}; - hls = (import (builtins.fetchTarball "https://github.com/shajra/nix-hls/tarball/master") {ghcVersion = "ghc884";}); + # hls = (import (builtins.fetchTarball "https://github.com/shajra/nix-hls/tarball/master") {ghcVersion = "ghc884";}); in pkgs.mkShell { buildInputs = [ @@ -16,8 +16,13 @@ pkgs.mkShell { pkgs.stack pkgs.dhall pkgs.glibc - hls.hls + # hls.hls pkgs.haskellPackages.ghcide dhall.linux-dhall-lsp-server + pkgs.haskellPackages.ormolu ]; + shellHook = '' + export TEST="IN HERE!" + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${pkgs.glibc}/lib + ''; } diff --git a/src/Actions/ActionTypes.hs b/src/Actions/ActionTypes.hs index 7b4ae84..a020679 100644 --- a/src/Actions/ActionTypes.hs +++ b/src/Actions/ActionTypes.hs @@ -1,10 +1,10 @@ -{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveAnyClass #-} module Actions.ActionTypes where -import Standard -import Dhall (Interpret) -import FocusList +import Dhall (Interpret) +import FocusList +import Standard -- | Actions/events to be performed data Action @@ -38,11 +38,12 @@ data Action deriving (Show, Generic, Interpret) -- | Modes similar to modes in vim -data Mode = NewMode { modeName :: Text - , hasButtons :: Bool - , hasBorders :: Bool - } +data Mode = NewMode + { modeName :: Text, + hasButtons :: Bool, + hasBorders :: Bool + } deriving (Show, Generic, Interpret) + instance Eq Mode where n1 == n2 = modeName n1 == modeName n2 - diff --git a/src/Base/DoAll.hs b/src/Base/DoAll.hs index 7608c1d..dcba5b7 100644 --- a/src/Base/DoAll.hs +++ b/src/Base/DoAll.hs @@ -1,112 +1,117 @@ +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} --- |There's a lot going on in this module and it can be really confusing if you --- aren't familiar with some fairly new Haskell extensions. Xest's effect --- system is provided by the Capability library. Capability provides what is --- essentially the ReaderT pattern with some code for reducing --- boilerplate. The boilerplate reducing code depends on deriving via, a GHC --- extension, and a bunch of newtypes exported by the library. To further --- reduce the boilerplate, I've used a Template Haskell function for some of --- the derivations. +-- | There's a lot going on in this module and it can be really confusing if you +-- aren't familiar with some fairly new Haskell extensions. Xest's effect +-- system is provided by the Capability library. Capability provides what is +-- essentially the ReaderT pattern with some code for reducing +-- boilerplate. The boilerplate reducing code depends on deriving via, a GHC +-- extension, and a bunch of newtypes exported by the library. To further +-- reduce the boilerplate, I've used a Template Haskell function for some of +-- the derivations. module Base.DoAll - ( module Base.Helpers - , module Base.Other - , module Base.Mover - , module Base.Property - , module Base.Minimizer - , module Base.Executor - , module Base.Global - , module Base.EventFlags - , module Base.Colorer - , module Base.Unmanaged - , module Base.DoAll + ( module Base.Helpers, + module Base.Other, + module Base.Mover, + module Base.Property, + module Base.Minimizer, + module Base.Executor, + module Base.Global, + module Base.EventFlags, + module Base.Colorer, + module Base.Unmanaged, + module Base.DoAll, ) where -import Standard -import Graphics.X11.Xlib.Types -import Graphics.X11.Xlib.Extras -import Graphics.X11.Types -import qualified Data.Map as M -import qualified SDL -import Base.Helpers -import Base.Other -import Base.Mover -import Base.Property -import Base.Minimizer -import Base.Executor -import Base.Global -import Base.Colorer -import Base.EventFlags -import Base.Unmanaged -import Tiler.TilerTypes -import Tiler.ParentChild -import TH -import Config -import Actions.ActionTypes -import qualified SDL.Font as Font -import qualified Control.Monad.Reader as R -import Graphics.X11.Xinerama (XineramaScreenInfo) +import Actions.ActionTypes +import Base.Colorer +import Base.EventFlags +import Base.Executor +import Base.Global +import Base.Helpers +import Base.Minimizer +import Base.Mover +import Base.Other +import Base.Property +import Base.Unmanaged import Capability.Reader () import Capability.State () +import Config +import qualified Control.Monad.Reader as R +import qualified Data.Map as M +import Graphics.X11.Types +import Graphics.X11.Xinerama (XineramaScreenInfo) +import Graphics.X11.Xlib.Extras +import Graphics.X11.Xlib.Types +import qualified SDL +import qualified SDL.Font as Font +import Standard +import TH +import Tiler.ParentChild +import Tiler.TilerTypes --- |Defines what kind of action triggered the KeyStatus Temp mode. +-- | Defines what kind of action triggered the KeyStatus Temp mode. data TempType = FromMod | NotMod - deriving Show + deriving (Show) --- |Defines the states for the keybinding state machine. For more info about --- how this is used, look in XEvents. +-- | Defines the states for the keybinding state machine. For more info about +-- how this is used, look in XEvents. data KeyStatus = New KeyStatus Mode KeyCode [Action] | Temp TempType KeyStatus Mode KeyCode [Action] | Default - deriving Show -makeBaseFunctor ''KeyStatus + deriving (Show) +makeBaseFunctor ''KeyStatus --- |The large record Xest uses in the ReaderT type. +-- | The large record Xest uses in the ReaderT type. data Ctx = Ctx - { shouldLog :: IORef (Bool) - , logHistory :: IORef [Text] - , activeMode :: IORef Mode - , screenList :: IORef Screens - , keyStatus :: IORef KeyStatus - , yankBuffer :: IORef [SubTiler] - , oldMouseButtons :: IORef OldMouseButtons - , atomNameCache :: IORef (M.Map Text Atom) - , atomValueCache :: IORef (M.Map Atom [Int]) - , focusedWindow :: IORef FocusedCache - , borderLocations :: IORef (M.Map SDL.Window XRect) - , windowLocations :: IORef (M.Map Window XRect) - , windowChildren :: IORef (M.Map Window [ParentChild]) - , stackCache :: IORef [Window] - , shouldRedraw :: IORef (Maybe ()) - , configuration :: IORef Conf - , activeScreen :: IORef ActiveScreen - , lastTime :: IORef OldTime - , knownDocks :: IORef Docks - , dockState :: IORef DockState - , rootWindow :: Window - , display :: Display - , fontChoice :: Font.Font - , cursor :: XCursor - } deriving (Generic) + { shouldLog :: IORef (Bool), + logHistory :: IORef [Text], + activeMode :: IORef Mode, + screenList :: IORef Screens, + keyStatus :: IORef KeyStatus, + yankBuffer :: IORef [SubTiler], + oldMouseButtons :: IORef OldMouseButtons, + atomNameCache :: IORef (M.Map Text Atom), + atomValueCache :: IORef (M.Map Atom [Int]), + focusedWindow :: IORef FocusedCache, + borderLocations :: IORef (M.Map SDL.Window XRect), + windowLocations :: IORef (M.Map Window XRect), + windowChildren :: IORef (M.Map Window [ParentChild]), + stackCache :: IORef [Window], + shouldRedraw :: IORef (Maybe ()), + configuration :: IORef Conf, + activeScreen :: IORef ActiveScreen, + lastTime :: IORef OldTime, + knownDocks :: IORef Docks, + dockState :: IORef DockState, + rootWindow :: Window, + display :: Display, + fontChoice :: Font.Font, + cursor :: XCursor + } + deriving (Generic) -- Some helper type synonyms for creating Input/Output/State instances for -- items in Ctx. type FromInput name = Rename name (Field name () (MonadReader M)) + type From name = ReaderIORef (FromInput name) + type Logged name s = LoggedSink name s (From name) M -- Type aliases that should be used elsewhere but for now are just used to get -- easy to access names when deriving Input/Output/State over these types. type ShouldRedraw = Maybe () + type Yanked = [SubTiler] + type LostWindow = Map Window [ParentChild] -- The Main Monad for Xest. -newtype M a = M { runM :: R.ReaderT Ctx IO a } +newtype M a = M {runM :: R.ReaderT Ctx IO a} deriving (Functor, Applicative, Monad, MonadIO, R.MonadReader Ctx) deriving (Input NewBorders) via FakeBorders M deriving (Input MouseButtons) via FakeMouseButtons M @@ -122,49 +127,48 @@ newtype M a = M { runM :: R.ReaderT Ctx IO a } -- much about the syntax/meta programming that's going on here. The template -- haskell code expands to roughly the deriving lines up above but with Output -- and State added to the deriving list. -generateIOS ''M ''LogLines [t| (From "logHistory") |] -generateIOS ''M ''Bool [t| (From "shouldLog") |] -generateIOS ''M ''Mode [t| (Logged "activeMode" Mode) |] -generateIOS ''M ''Screens [t| (Logged "screenList" Screens) |] -generateIOS ''M ''Yanked [t| (Logged "yankBuffer" [SubTiler]) |] -generateIOS ''M ''OldMouseButtons [t| (Logged "oldMouseButtons" OldMouseButtons) |] -generateIOS ''M ''AtomCache [t| (Logged "atomNameCache" (M.Map Text Atom)) |] -generateIOS ''M ''RootPropCache [t| (Logged "atomValueCache" (M.Map Atom [Int])) |] -generateIOS ''M ''WindowStack [t| (Logged "stackCache" [Window]) |] -generateIOS ''M ''FocusedCache [t| (Logged "focusedWindow" FocusedCache) |] -generateIOS ''M ''SDLLocCache [t| (Logged "borderLocations" SDLLocCache) |] -generateIOS ''M ''LocCache [t| (Logged "windowLocations" LocCache) |] -generateIOS ''M ''LostWindow [t| (Logged "windowChildren" LostWindow) |] -generateIOS ''M ''ShouldRedraw [t| (Logged "shouldRedraw" ShouldRedraw) |] -generateIOS ''M ''Conf [t| (Logged "configuration" Conf) |] -generateIOS ''M ''ActiveScreen [t| (Logged "activeScreen" ActiveScreen) |] -generateIOS ''M ''OldTime [t| (Logged "lastTime" OldTime) |] -generateIOS ''M ''Docks [t| (Logged "knownDocks" Docks) |] -generateIOS ''M ''DockState [t| (Logged "dockState" DockState) |] -generateIOS ''M ''KeyStatus [t| (Logged "keyStatus" KeyStatus) |] -generateIOS ''M ''Tiler [t| FakeTiler M |] -generateIOS ''M ''SubTiler [t| Coerce SubTiler M |] +generateIOS ''M ''LogLines [t|(From "logHistory")|] +generateIOS ''M ''Bool [t|(From "shouldLog")|] +generateIOS ''M ''Mode [t|(Logged "activeMode" Mode)|] +generateIOS ''M ''Screens [t|(Logged "screenList" Screens)|] +generateIOS ''M ''Yanked [t|(Logged "yankBuffer" [SubTiler])|] +generateIOS ''M ''OldMouseButtons [t|(Logged "oldMouseButtons" OldMouseButtons)|] +generateIOS ''M ''AtomCache [t|(Logged "atomNameCache" (M.Map Text Atom))|] +generateIOS ''M ''RootPropCache [t|(Logged "atomValueCache" (M.Map Atom [Int]))|] +generateIOS ''M ''WindowStack [t|(Logged "stackCache" [Window])|] +generateIOS ''M ''FocusedCache [t|(Logged "focusedWindow" FocusedCache)|] +generateIOS ''M ''SDLLocCache [t|(Logged "borderLocations" SDLLocCache)|] +generateIOS ''M ''LocCache [t|(Logged "windowLocations" LocCache)|] +generateIOS ''M ''LostWindow [t|(Logged "windowChildren" LostWindow)|] +generateIOS ''M ''ShouldRedraw [t|(Logged "shouldRedraw" ShouldRedraw)|] +generateIOS ''M ''Conf [t|(Logged "configuration" Conf)|] +generateIOS ''M ''ActiveScreen [t|(Logged "activeScreen" ActiveScreen)|] +generateIOS ''M ''OldTime [t|(Logged "lastTime" OldTime)|] +generateIOS ''M ''Docks [t|(Logged "knownDocks" Docks)|] +generateIOS ''M ''DockState [t|(Logged "dockState" DockState)|] +generateIOS ''M ''KeyStatus [t|(Logged "keyStatus" KeyStatus)|] +generateIOS ''M ''Tiler [t|FakeTiler M|] +generateIOS ''M ''SubTiler [t|Coerce SubTiler M|] deriving via Logger M instance HasSink LogData LogData M - -- Want to do everything in IO? Use this! -doAll - :: IORef [Text] - -> Screens - -> Conf - -> Mode - -> Display - -> Window - -> Font.Font - -> Cursor - -> M a -- The super long Monad which GHC can figure out on its own - -> IO a +doAll :: + IORef [Text] -> + Screens -> + Conf -> + Mode -> + Display -> + Window -> + Font.Font -> + Cursor -> + M a -> -- The super long Monad which GHC can figure out on its own + IO a doAll ioref t c m d w f cur mon = do shouldLog <- newIORef False logHistory <- pure ioref activeMode <- newIORef m - yankBuffer <- newIORef [] + yankBuffer <- newIORef [] oldMouseButtons <- newIORef $ OMB None atomNameCache <- newIORef M.empty atomValueCache <- newIORef M.empty @@ -185,4 +189,4 @@ doAll ioref t c m d w f cur mon = do display <- pure d fontChoice <- pure f cursor <- pure $ XCursor cur - R.runReaderT (runM mon) $ Ctx {..} \ No newline at end of file + R.runReaderT (runM mon) $ Ctx {..} diff --git a/src/Base/Helpers.hs b/src/Base/Helpers.hs index 8a52816..54397a4 100644 --- a/src/Base/Helpers.hs +++ b/src/Base/Helpers.hs @@ -1,28 +1,26 @@ module Base.Helpers where -import Standard -import Data.Kind -import Graphics.X11.Types - +import Data.Kind +import Graphics.X11.Types +import Standard type family TypeMap (f :: a -> a -> b) (xs :: [a]) where - TypeMap _ '[] = '[] - TypeMap f (x ': xs) = f x x ': TypeMap f xs + TypeMap _ '[] = '[] + TypeMap f (x ': xs) = f x x ': TypeMap f xs type family (++) (a :: [t]) (b :: [t]) where (++) '[] b = b (++) (a ': as) b = a ': (as ++ b) - --- |A type level function which takes a list of Types and turns them into --- inputs. +-- | A type level function which takes a list of Types and turns them into +-- inputs. type Inputs (a :: [Type]) = TypeMap HasSource a - --- |Same as above but for State. +-- | Same as above but for State. type States (a :: [Type]) = TypeMap HasState a + newtype OldTime = OldTime Time deriving (Show) --- |Just makes it more clear when you say Input RootWindow. +-- | Just makes it more clear when you say Input RootWindow. type RootWindow = Window diff --git a/src/Config.hs b/src/Config.hs index 22cc748..163c489 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -1,33 +1,34 @@ -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE RecordWildCards #-} module Config where -import Standard hiding (input) -import Dhall -import Graphics.X11.Xlib.Misc -import Graphics.X11.Xlib.Types -import Graphics.X11.Types -import Actions.ActionTypes -import System.Process -import qualified System.Environment as Env +import Actions.ActionTypes +import Dhall +import Graphics.X11.Types +import Graphics.X11.Xlib.Misc +import Graphics.X11.Xlib.Types +import Standard hiding (input) import System.Directory +import qualified System.Environment as Env +import System.Process --- |A config file. It contains a list of startup scripts, a command to run, and --- an initial mode. The a type decides how you want to represent keys. --- internally, we store those as KeyCodes but users would prefer to specify --- them as strings. -data ConfA a = Conf { keyBindings :: [KeyTrigger a] - , startupScript :: Text - , initialMode :: Mode - , fontLocation :: Text - } +-- | A config file. It contains a list of startup scripts, a command to run, and +-- an initial mode. The a type decides how you want to represent keys. +-- internally, we store those as KeyCodes but users would prefer to specify +-- them as strings. +data ConfA a = Conf + { keyBindings :: [KeyTrigger a], + startupScript :: Text, + initialMode :: Mode, + fontLocation :: Text + } deriving (Generic, Show, Interpret) --- |The internal Config file. +-- | The internal Config file. type Conf = ConfA KeyCode --- |The user style config file. +-- | The user style config file. type ConfUser = ConfA Text -- | Convert a ConfUser to a Conf. The Bool tells us if we're reloading or @@ -39,16 +40,17 @@ confToType display (Conf kb startupScript@(Text script) initialMode fontLocation unless isReload $ do exitCode <- runCommand script >>= waitForProcess - unless (exitCode == ExitSuccess) - $ die "Error while running startup script" - return Conf { .. } + unless (exitCode == ExitSuccess) $ + die "Error while running startup script" + return Conf {..} -- | Represents a keybinding in Xest. -data KeyTrigger a = KeyTrigger { key :: a - , mode :: Mode - , actions :: [Action] - , exitActions :: [Action] - } +data KeyTrigger a = KeyTrigger + { key :: a, + mode :: Mode, + actions :: [Action], + exitActions :: [Action] + } deriving (Generic, Show, Interpret, Functor, Traversable, Foldable) findConfFile :: IO Text @@ -58,7 +60,7 @@ findConfFile = do let baseDirs = [homeDir ++ "/.config", "/etc"] let suffixes = [displayNumber ++ ".dhall", "dhall"] print $ baseDirs ++ suffixes - Just configLoc <- findMOf each doesFileExist [ base <> "/xest/config." <> suffix | base <- baseDirs, suffix <- suffixes] + Just configLoc <- findMOf each doesFileExist [base <> "/xest/config." <> suffix | base <- baseDirs, suffix <- suffixes] return $ Text configLoc -- | A simple function to open a config file and parse it. diff --git a/src/FocusList.hs b/src/FocusList.hs index 423038e..76659cd 100644 --- a/src/FocusList.hs +++ b/src/FocusList.hs @@ -1,223 +1,244 @@ -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE OverloadedLists #-} -{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} module FocusList - ( FocusedList(..) - , Direction(..) - , Focus(..) - , push - , pop - , flMapMaybe - , flLength - , vOrder - , fOrder - , mapOne - , focusElem - , focusIndex - , focusVIndex - , visualIndex - , visualFIndex - , findNeFocIndex - , makeFL - , focusDir - , indexFL - , fromFoc - , fromVis + ( FocusedList (..), + Direction (..), + Focus (..), + push, + pop, + flMapMaybe, + flLength, + vOrder, + fOrder, + mapOne, + focusElem, + focusIndex, + focusVIndex, + visualIndex, + visualFIndex, + findNeFocIndex, + makeFL, + focusDir, + indexFL, + fromFoc, + fromVis, ) where -import Standard hiding (zip, zipWith) -import Data.ChunkedZip -import Data.List.NonEmpty (fromList) -import Text.Show.Deriving -import Data.Eq.Deriving -import Dhall (Interpret) +import Data.ChunkedZip +import Data.Eq.Deriving +import Data.List.NonEmpty (fromList) +import Dhall (Interpret) +import Standard hiding (zip, zipWith) +import Text.Show.Deriving -- I am super unattached to all of the code in this module. -- If someone has a better way to represent this, I would gladly switch. --- |Meant to represent the Head and Last on the list when sorted in focus order +-- | Meant to represent the Head and Last on the list when sorted in focus order data Focus = Focused | Unfocused deriving stock (Eq, Generic, Show) --- |Meant to represent the Head and Last on the list when sorted in visual order +-- | Meant to represent the Head and Last on the list when sorted in visual order data Direction = Front | Back deriving (Show, Eq, Generic, Interpret) --- |Things that are assumed about a Focused List but aren't proven: --- 1. The orders actually point to valid indices --- 2. The lists are of the same size --- 3. *Order doesn't contain duplicates -data FocusedList a = FL { visualOrder :: NonEmpty Int - , focusOrder :: NonEmpty Int - , actualData :: NonEmpty a - } - --- Begin deriving the laundry list of things we want to use +-- | Things that are assumed about a Focused List but aren't proven: +-- 1. The orders actually point to valid indices +-- 2. The lists are of the same size +-- 3. *Order doesn't contain duplicates +data FocusedList a = FL + { visualOrder :: NonEmpty Int, + focusOrder :: NonEmpty Int, + actualData :: NonEmpty a + } + -- Begin deriving the laundry list of things we want to use deriving (Eq, Show, Functor, Generic, Foldable, Traversable) + deriveShow1 ''FocusedList deriveEq1 ''FocusedList instance Zip FocusedList where - zipWith f fl@FL { actualData = ad } FL { actualData = add } = - fl { actualData = zipWith f ad add } - + zipWith f fl@FL {actualData = ad} FL {actualData = add} = + fl {actualData = zipWith f ad add} -- Begin the actual code -- | Pushes something to different ends push :: Direction -> Focus -> a -> FocusedList a -> FocusedList a -push dir foc a FL { visualOrder = vo, focusOrder = fo, actualData = ad } = FL - { visualOrder = dirSide - , focusOrder = focSide - , actualData = ad <> [a] - } - where - focSide = if foc == Focused then len <| fo else fo <> [len] - dirSide = if dir == Front then len <| vo else vo <> [len] - len = length ad +push dir foc a FL {visualOrder = vo, focusOrder = fo, actualData = ad} = + FL + { visualOrder = dirSide, + focusOrder = focSide, + actualData = ad <> [a] + } + where + focSide = if foc == Focused then len <| fo else fo <> [len] + dirSide = if dir == Front then len <| vo else vo <> [len] + len = length ad -- | Given an ordering and direction to pop from, pops from the list pop :: Either Direction Focus -> FocusedList a -> (a, Maybe (FocusedList a)) -pop (Right isFocused) FL { visualOrder = vo, focusOrder = fo, actualData = ad } - = case isFocused of - Focused -> popLogic head (nonEmpty . tail) - Unfocused -> popLogic last (nonEmpty . init) - where popLogic elemF otherF = - (ad !! elemF fo,) $ do - tailFo <- otherF fo - filteredVo <- remove (elemF fo) vo - filteredAd <- removeAt (elemF fo) ad - return $ reduce (elemF fo) FL { visualOrder = filteredVo - , focusOrder = tailFo - , actualData = filteredAd - } - -pop (Left direction) FL { visualOrder = vo, focusOrder = fo, actualData = ad } - = case direction of - Front -> popLogic head (nonEmpty . tail) - Back -> popLogic last (nonEmpty . init) - where popLogic elemV otherV = - (ad !! elemV vo,) $ do - tailVo <- otherV vo - filteredFo <- remove (elemV vo) fo - filteredAd <- removeAt (elemV vo) ad - return $ reduce (elemV vo) FL { visualOrder = tailVo - , focusOrder = filteredFo - , actualData = filteredAd - } - - --- |We just removed something from the list which did all sorts of bad things to order. --- This function fixes the indices so we don't have holes. +pop (Right isFocused) FL {visualOrder = vo, focusOrder = fo, actualData = ad} = + case isFocused of + Focused -> popLogic head (nonEmpty . tail) + Unfocused -> popLogic last (nonEmpty . init) + where + popLogic elemF otherF = + (ad !! elemF fo,) $ do + tailFo <- otherF fo + filteredVo <- remove (elemF fo) vo + filteredAd <- removeAt (elemF fo) ad + return $ + reduce + (elemF fo) + FL + { visualOrder = filteredVo, + focusOrder = tailFo, + actualData = filteredAd + } +pop (Left direction) FL {visualOrder = vo, focusOrder = fo, actualData = ad} = + case direction of + Front -> popLogic head (nonEmpty . tail) + Back -> popLogic last (nonEmpty . init) + where + popLogic elemV otherV = + (ad !! elemV vo,) $ do + tailVo <- otherV vo + filteredFo <- remove (elemV vo) fo + filteredAd <- removeAt (elemV vo) ad + return $ + reduce + (elemV vo) + FL + { visualOrder = tailVo, + focusOrder = filteredFo, + actualData = filteredAd + } + +-- | We just removed something from the list which did all sorts of bad things to order. +-- This function fixes the indices so we don't have holes. reduce :: Int -> FocusedList a -> FocusedList a -reduce removed fl@FL {..} = fl { focusOrder = newL focusOrder - , visualOrder = newL visualOrder - } - where newL = map (\i -> if i > removed then i - 1 else i) - +reduce removed fl@FL {..} = + fl + { focusOrder = newL focusOrder, + visualOrder = newL visualOrder + } + where + newL = map (\i -> if i > removed then i - 1 else i) -- | Modify one of the 4 ends mapOne :: Either Direction Focus -> (a -> a) -> FocusedList a -> FocusedList a -mapOne orderAndEnd f fl@FL { focusOrder = fo, visualOrder = vo, actualData = ad } = fl - { actualData = case orderAndEnd of - Left Front -> mapEnd $ head vo - Left Back -> mapEnd $ last vo - Right Focused -> mapEnd $ head fo - Right Unfocused -> mapEnd $ last fo - } - where mapEnd tarfindNeI = map (\(i, a) -> if i == tarfindNeI then f a else a) $ zip [0 ..] ad +mapOne orderAndEnd f fl@FL {focusOrder = fo, visualOrder = vo, actualData = ad} = + fl + { actualData = case orderAndEnd of + Left Front -> mapEnd $ head vo + Left Back -> mapEnd $ last vo + Right Focused -> mapEnd $ head fo + Right Unfocused -> mapEnd $ last fo + } + where + mapEnd tarfindNeI = map (\(i, a) -> if i == tarfindNeI then f a else a) $ zip [0 ..] ad -- | Filter a focused list. Unfortunately, filter isn't a typeclass anywhere -- TODO This looks suspiciously like traverse... flMapMaybe :: (a -> Maybe b) -> FocusedList a -> Maybe (FocusedList b) -flMapMaybe predicate FL { actualData = ad, visualOrder = vo, focusOrder = fo } = +flMapMaybe predicate FL {actualData = ad, visualOrder = vo, focusOrder = fo} = map (\unwrapped -> foldl' (flip reduce) unwrapped $ sortBy (comparing Down) gone) newFL where newFL = do newAd <- nonEmpty $ mapMaybe predicate $ toList ad newVo <- foldM removeFrom vo gone newFo <- foldM removeFrom fo gone - return FL { actualData = newAd - , visualOrder = newVo - , focusOrder = newFo - } - gone = foldl' (\acc (i, a) -> if isJust $ predicate a then acc else i : acc) [] - $ zip [0 ..] ad + return + FL + { actualData = newAd, + visualOrder = newVo, + focusOrder = newFo + } + gone = + foldl' (\acc (i, a) -> if isJust $ predicate a then acc else i : acc) [] $ + zip [0 ..] ad removeFrom = flip remove flLength :: FocusedList a -> Int flLength FL {..} = length actualData vOrder :: FocusedList a -> NonEmpty a -vOrder FL { visualOrder = vo, actualData = ad } = +vOrder FL {visualOrder = vo, actualData = ad} = map ((!!) ad) vo fOrder :: FocusedList a -> NonEmpty a -fOrder FL { focusOrder = fo, actualData = ad } = +fOrder FL {focusOrder = fo, actualData = ad} = map ((!!) ad) fo focusElem :: (a -> Bool) -> FocusedList a -> FocusedList a -focusElem p fl@FL { actualData = ad } = focusIndex loc fl +focusElem p fl@FL {actualData = ad} = focusIndex loc fl where - loc = fst $ fromJust $ find (p . snd) $ mzip [0..] ad + loc = fst $ fromJust $ find (p . snd) $ mzip [0 ..] ad focusIndex :: Int -> FocusedList a -> FocusedList a -focusIndex i fl@FL { focusOrder = fo } = fl - { focusOrder = if length fo > i then focusNE i fo else fo - } +focusIndex i fl@FL {focusOrder = fo} = + fl + { focusOrder = if length fo > i then focusNE i fo else fo + } visualIndex :: Int -> FocusedList a -> FocusedList a -visualIndex i fl@FL { visualOrder = vo } = fl - { visualOrder = if length vo > i then focusNE i vo else vo - } +visualIndex i fl@FL {visualOrder = vo} = + fl + { visualOrder = if length vo > i then focusNE i vo else vo + } focusNE :: Int -> NonEmpty Int -> NonEmpty Int focusNE i = maybe (pure i) ((<|) i) . remove i - focusVIndex :: Int -> FocusedList a -> FocusedList a -focusVIndex i fl@FL {visualOrder = vo } = focusIndex (vo !! i) fl +focusVIndex i fl@FL {visualOrder = vo} = focusIndex (vo !! i) fl visualFIndex :: Int -> FocusedList a -> FocusedList a -visualFIndex i fl@FL {focusOrder = fo } = visualIndex (fo !! i) fl +visualFIndex i fl@FL {focusOrder = fo} = visualIndex (fo !! i) fl findNeFocIndex :: FocusedList a -> Int findNeFocIndex FL {..} = fromJust $ findIndex (== head focusOrder) $ toList visualOrder makeFL :: NonEmpty a -> Int -> FocusedList a -makeFL actualData focIndex = FL { visualOrder = vo - , focusOrder = fo - , actualData - } - where - len = length actualData - vo = [0.. len - 1] - fo = maybe (pure focIndex) ((<|) focIndex) $ removeAt focIndex vo +makeFL actualData focIndex = + FL + { visualOrder = vo, + focusOrder = fo, + actualData + } + where + len = length actualData + vo = [0 .. len - 1] + fo = maybe (pure focIndex) ((<|) focIndex) $ removeAt focIndex vo focusDir :: Direction -> FocusedList a -> FocusedList a -focusDir dir fl@FL { focusOrder = fo, visualOrder = vo } = fromMaybe fl $ +focusDir dir fl@FL {focusOrder = fo, visualOrder = vo} = fromMaybe fl $ case dir of Back -> switchF Just (nonEmpty . tail) Front -> switchF (nonEmpty . tail) Just - where switchF finding using = do - findList <- finding vo - usingList <- using vo - newLoc <- find ((== head fo) . fst) $ zip findList usingList - return $ focusIndex (snd newLoc) fl + where + switchF finding using = do + findList <- finding vo + usingList <- using vo + newLoc <- find ((== head fo) . fst) $ zip findList usingList + return $ focusIndex (snd newLoc) fl indexFL :: Int -> FocusedList a -> a indexFL i FL {..} = actualData !! i reconcile :: NonEmpty b -> NonEmpty Int -> FocusedList a -> FocusedList b -reconcile newAs order fl@FL{..} = - fl {actualData = foldl' updateAt base $ zip order newAs} - where updateAt as (i, a) = map (\old -> if fst old == i then a else snd old) $ zip (0 :| [1..]) as - base = fromList $ replicate (length actualData) $ head newAs +reconcile newAs order fl@FL {..} = + fl {actualData = foldl' updateAt base $ zip order newAs} + where + updateAt as (i, a) = map (\old -> if fst old == i then a else snd old) $ zip (0 :| [1 ..]) as + base = fromList $ replicate (length actualData) $ head newAs fromFoc :: FocusedList a -> NonEmpty b -> FocusedList b fromFoc oldFl as = reconcile as (focusOrder oldFl) oldFl diff --git a/src/Lib.hs b/src/Lib.hs index 6fd9fe1..a03e248 100644 --- a/src/Lib.hs +++ b/src/Lib.hs @@ -1,31 +1,31 @@ -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE RecordWildCards #-} module Lib - ( startWM + ( startWM, ) where -import Standard -import Config -import Core -import Graphics.X11.Types -import Graphics.X11.Xlib.Display -import Graphics.X11.Xlib.Event -import Graphics.X11.Xlib.Extras -import Graphics.X11.Xlib.Misc -import Graphics.X11.Xlib.Window -import SDL hiding (get, Window, Display, trace, Mode, Event) -import qualified SDL.Font as Font -import Base.DoAll -import Tiler.Tiler -import qualified Data.IntMap as IM -import XEvents import Actions.ActionTypes import Actions.Actions -import Text.Regex (mkRegex, subRegex) +import Base.DoAll +import Config import qualified Control.Exception as E +import Core +import qualified Data.IntMap as IM +import Graphics.X11.Types +import Graphics.X11.Xlib.Display +import Graphics.X11.Xlib.Event +import Graphics.X11.Xlib.Extras +import Graphics.X11.Xlib.Misc +import Graphics.X11.Xlib.Window +import SDL hiding (Display, Event, Mode, Window, get, trace) +import qualified SDL.Font as Font +import Standard import qualified System.Environment as Env +import Text.Regex (mkRegex, subRegex) +import Tiler.Tiler +import XEvents -- | Wraps Xest in some basic logging and error handling. startWM :: IO () @@ -33,9 +33,11 @@ startWM = do -- This should only get filled if some part of initialization fails E.catch runWM \(e :: SomeException) -> do writeFile "/tmp/xest_init.err" $ Text $ filterAnsi $ displayException e - where ansiRegex = mkRegex "\\[[0-9;?]+m" - filterAnsi line = subRegex ansiRegex stripped "" - where stripped = mfilter (/= '\ESC') line + where + ansiRegex = mkRegex "\\[[0-9;?]+m" + filterAnsi line = subRegex ansiRegex stripped "" + where + stripped = mfilter (/= '\ESC') line -- | Starting point of the program. This function should never return runWM :: IO () @@ -65,7 +67,7 @@ runWM = do -- X orders windows like a tree. -- This gets the root of said tree. let root = defaultRootWindow display - + -- Set the cursor for the root window -- 132 is the magic number for the normal arrow cursor <- createFontCursor display 132 @@ -76,28 +78,36 @@ runWM = do -- Since we don't want the window to be visible, we give it a crazy -- location. We alse set_override_redirect because Xest shouldn't be -- alerted if the window gets moved around. - ewmhWin <- createSimpleWindow display root - 10000 10000 1 1 0 0 - $ whitePixel display (defaultScreen display) + ewmhWin <- + createSimpleWindow + display + root + 10000 + 10000 + 1 + 1 + 0 + 0 + $ whitePixel display (defaultScreen display) allocaSetWindowAttributes $ \wa -> set_override_redirect wa True - >> changeWindowAttributes display ewmhWin cWOverrideRedirect wa + >> changeWindowAttributes display ewmhWin cWOverrideRedirect wa mapWindow display ewmhWin logHistory <- newIORef [] - + -- Find and register ourselves with the root window -- These masks allow us to intercept various Xorg events useful for a WM - selectInput display root - $ substructureNotifyMask - .|. substructureRedirectMask - .|. structureNotifyMask - .|. leaveWindowMask - .|. enterWindowMask - .|. buttonPressMask - .|. buttonReleaseMask - .|. keyPressMask - .|. keyReleaseMask + selectInput display root $ + substructureNotifyMask + .|. substructureRedirectMask + .|. structureNotifyMask + .|. leaveWindowMask + .|. enterWindowMask + .|. buttonPressMask + .|. buttonReleaseMask + .|. keyPressMask + .|. keyReleaseMask -- Grabs the initial keybindings and screen list while also setting up EWMH screens <- doAll logHistory IM.empty c startingMode display root font cursor $ do @@ -106,7 +116,7 @@ runWM = do rootChange get @Screens - -- Normally, Xlib will crash on any error. Calling this function + -- Normally, Xlib will crash on any error. Calling this function -- asks Xlib to print recoverable errors instead of crashing on them. setDefaultErrorHandler -- xSetErrorHandler @@ -122,8 +132,6 @@ runWM = do let header = "Xest crashed with the exception: " <> Text (displayException e) <> "\n" writeFile "/tmp/xest.err" $ header <> lastLog <> "\n" - - -- | Performs the main logic. Does it all! mainLoop :: Event -> M () mainLoop event = do @@ -141,13 +149,12 @@ mainLoop event = do nwwt <- getAtom False "_NET_WM_WINDOW_TYPE" windowType <- getProperty 32 nwwt ev_window if elem nwwtd windowType - then - addUM ev_window >> put @(Maybe ()) (Just ()) + then addUM ev_window >> put @(Maybe ()) (Just ()) else do rootTiler <- get @Tiler unless (findWindow ev_window rootTiler) $ reparentWin ev_window >>= mapWin - + -- Called when a window actually dies. DestroyWindowEvent {..} -> killed ev_window -- Called when a window is dying. Because X is asynchronous, there's a chance @@ -164,9 +171,10 @@ mainLoop event = do root <- input @RootWindow -- Why the if statement? Well we want to focus the root window -- if no other windows are currently focused. - if | ev_event_type == enterNotify -> newFocus ev_window - | ev_window == root -> newFocus root - | otherwise -> return () + if + | ev_event_type == enterNotify -> newFocus ev_window + | ev_window == root -> newFocus root + | otherwise -> return () -- Button in this case means mouse button. Used to trigger click to focus. ButtonEvent {..} -> do put @OldTime (OldTime ev_time) @@ -194,47 +202,44 @@ mainLoop event = do when (wm_state == ev_message_type && full `elem` ev_data) $ makeFullscreen ev_window isSet - -- 21 == reparent event. If a window decides to reparent itself, -- it's practically unmapped and dead. AnyEvent {ev_event_type = 21, ev_window = window} -> killed window - _ -> void $ log $ LD "Event" "Got unknown event" -- Move all of the windows based on how our internal state changed refreshRequested <- isJust <$> get @(Maybe ()) when refreshRequested refresh - - where -- Here we have executors for the various actions a user might -- have in their config. These go to Actions/Actions.hs executeActions :: Action -> M () - executeActions action = log (LD "Action" $ show action) >> case action of - RunCommand command -> execute command - ShowWindow wName -> getWindowByClass wName >>= mapM_ restore - HideWindow wName -> getWindowByClass wName >>= mapM_ minimize - ZoomInInput -> zoomInInput - ZoomOutInput -> zoomOutInput - ZoomInMonitor -> zoomInMonitor - ZoomOutMonitor -> zoomOutMonitor - ZoomMonitorToInput -> zoomMonitorToInput - ZoomInputToMonitor -> zoomInputToMonitor - ChangeModeTo mode -> changeModeTo mode - Move dir -> changeMany $ moveDir dir - ChangeNamed (Text name) -> maybe (return ()) (changeMany . changeIndex) $ readMaybe name - PopTiler -> popTiler - PushTiler -> pushTiler - Insert -> insertTiler - MoveToFront -> changeMany moveToFront - MakeEmpty -> makeEmptySpot - KillActive -> killActive - ExitNow -> absurd <$> exit - ToggleLogging -> toggleLogs - ChangeToHorizontal -> changeMany toHoriz - ChangeToFloating -> changeMany toFloating - ChangeToTwoCols -> changeMany toTwoCols - SetRotate -> changeMods Rotate - SetFull -> changeMods Full - SetNoMod -> changeMods NoMods - ToggleDocks -> toggleDocks + executeActions action = + log (LD "Action" $ show action) >> case action of + RunCommand command -> execute command + ShowWindow wName -> getWindowByClass wName >>= mapM_ restore + HideWindow wName -> getWindowByClass wName >>= mapM_ minimize + ZoomInInput -> zoomInInput + ZoomOutInput -> zoomOutInput + ZoomInMonitor -> zoomInMonitor + ZoomOutMonitor -> zoomOutMonitor + ZoomMonitorToInput -> zoomMonitorToInput + ZoomInputToMonitor -> zoomInputToMonitor + ChangeModeTo mode -> changeModeTo mode + Move dir -> changeMany $ moveDir dir + ChangeNamed (Text name) -> maybe (return ()) (changeMany . changeIndex) $ readMaybe name + PopTiler -> popTiler + PushTiler -> pushTiler + Insert -> insertTiler + MoveToFront -> changeMany moveToFront + MakeEmpty -> makeEmptySpot + KillActive -> killActive + ExitNow -> absurd <$> exit + ToggleLogging -> toggleLogs + ChangeToHorizontal -> changeMany toHoriz + ChangeToFloating -> changeMany toFloating + ChangeToTwoCols -> changeMany toTwoCols + SetRotate -> changeMods Rotate + SetFull -> changeMods Full + SetNoMod -> changeMods NoMods + ToggleDocks -> toggleDocks diff --git a/src/Standard/Beam.hs b/src/Standard/Beam.hs index f014bc6..5f957fd 100644 --- a/src/Standard/Beam.hs +++ b/src/Standard/Beam.hs @@ -2,28 +2,28 @@ module Standard.Beam where -import Data.Functor.Foldable.TH import BasePrelude +import Data.Functor.Foldable.TH --- |You can think of a beam as the opposite of a list. instead of having --- 0 or more elements and a guaranteed empty case, Beam has 0 or more --- empty cases and a guaranteed element wrapped inside. +-- | You can think of a beam as the opposite of a list. instead of having +-- 0 or more elements and a guaranteed empty case, Beam has 0 or more +-- empty cases and a guaranteed element wrapped inside. -- --- You might be thinking, "This looks completely useluss!" and if we think of --- it as a normal container (like array, tree, list, etc.) then you would --- probably be right. If you think of Beam not as a container but as control --- flow, you get some cool results though. For an example, look at onInput . --- Instead of doing it recursively, you can use a hylomorphism to abstract away --- the recursion. At this point though, you need to pick a data type for F wich --- will wrap the intermediate result. What do you pick? Well Beam makes a --- great choice. For each recursive call, you just return Continue. Once you --- reach the leaf, you return End. Then, you can use a simple catamorphism to --- extract the value from the beam. +-- You might be thinking, "This looks completely useluss!" and if we think of +-- it as a normal container (like array, tree, list, etc.) then you would +-- probably be right. If you think of Beam not as a container but as control +-- flow, you get some cool results though. For an example, look at onInput . +-- Instead of doing it recursively, you can use a hylomorphism to abstract away +-- the recursion. At this point though, you need to pick a data type for F wich +-- will wrap the intermediate result. What do you pick? Well Beam makes a +-- great choice. For each recursive call, you just return Continue. Once you +-- reach the leaf, you return End. Then, you can use a simple catamorphism to +-- extract the value from the beam. data Beam a = End a | Continue (Beam a) deriving (Eq, Show, Functor) makeBaseFunctor ''Beam getEnd :: BeamF a a -> a -getEnd (EndF a) = a +getEnd (EndF a) = a getEnd (ContinueF a) = a diff --git a/src/Standard/RectA.hs b/src/Standard/RectA.hs index 51a336c..e62a9df 100644 --- a/src/Standard/RectA.hs +++ b/src/Standard/RectA.hs @@ -2,24 +2,26 @@ module Standard.RectA where -import Data.Bifunctor.TH import BasePrelude +import Data.Bifunctor.TH --- |A rectangle with any kind of dimension you could every want. The A stands --- for abstract. You probably want one of the type synonyms below. -data RectA a b = Rect { x :: a - , y :: a - , w :: b - , h :: b - } +-- | A rectangle with any kind of dimension you could every want. The A stands +-- for abstract. You probably want one of the type synonyms below. +data RectA a b = Rect + { x :: a, + y :: a, + w :: b, + h :: b + } deriving (Show, Eq) + deriveBifunctor ''RectA --- |A rectangle over Doubles. This is usually what people think of --- when you say rectangle. +-- | A rectangle over Doubles. This is usually what people think of +-- when you say rectangle. type Rect = RectA Double Double --- |A rectangle according to X11. +-- | A rectangle according to X11. type XRect = RectA Int32 Word32 -- TODO There must be some way to get the Compiler to make this one... diff --git a/src/Standard/Stream.hs b/src/Standard/Stream.hs index e54fb2d..239c95f 100644 --- a/src/Standard/Stream.hs +++ b/src/Standard/Stream.hs @@ -2,10 +2,10 @@ module Standard.Stream where import BasePrelude --- |This stream lets me return an infinite list of values where each value --- comes from some monadic computation. To understand why I need this, consider --- implementing filterStream on something of type [m a] where filterStream --- remains lazy. +-- | This stream lets me return an infinite list of values where each value +-- comes from some monadic computation. To understand why I need this, consider +-- implementing filterStream on something of type [m a] where filterStream +-- remains lazy. data Stream m a = Stream a (m (Stream m a)) filterStream :: Monad m => (a -> Bool) -> Stream m a -> m (Stream m a) diff --git a/src/Standard/Tagged.hs b/src/Standard/Tagged.hs index 85d8d61..17cdbc1 100644 --- a/src/Standard/Tagged.hs +++ b/src/Standard/Tagged.hs @@ -3,9 +3,9 @@ module Standard.Tagged where import BasePrelude import Control.Comonad --- |Tagged is a lot like Either except more optimistic. In Either, a single --- failure propogates upwards. With Tagged, a single Success propogates --- upwards. +-- | Tagged is a lot like Either except more optimistic. In Either, a single +-- failure propogates upwards. With Tagged, a single Success propogates +-- upwards. data Tagged a = Failed a | Succeeded a deriving (Show, Eq, Read, Functor, Foldable, Traversable) diff --git a/src/Standard/Transformation.hs b/src/Standard/Transformation.hs index a5c09c7..6b8e251 100644 --- a/src/Standard/Transformation.hs +++ b/src/Standard/Transformation.hs @@ -2,38 +2,38 @@ module Standard.Transformation where -import Data.Functor.Foldable.TH -import Data.Functor.Foldable import BasePrelude +import Data.Functor.Foldable +import Data.Functor.Foldable.TH import Standard.RectA --- |Some transformations you might want to make to a rectangle. --- Instead of just doing the transformation with something like: +-- | Some transformations you might want to make to a rectangle. +-- Instead of just doing the transformation with something like: -- --- "slide :: Rect -> Rect -> Rect" +-- "slide :: Rect -> Rect -> Rect" -- --- we create a list of transformations and wait to apply them until the --- last moment. This lets our drawing functions inspect how they're going --- to be transformed. +-- we create a list of transformations and wait to apply them until the +-- last moment. This lets our drawing functions inspect how they're going +-- to be transformed. data Transformation = Slide Rect Transformation | Spin Transformation | StartingPoint XRect deriving (Eq, Show) makeBaseFunctor ''Transformation --- |Actually does the computations to create a new rectangle. +-- | Actually does the computations to create a new rectangle. toScreenCoord :: Transformation -> XRect -toScreenCoord = bimap floor ceiling . snd . cata \case - SlideF (Rect dx dy dw dh) (False, Rect {..}) -> - (False, Rect (x + dx * w) (y + dy * h) (w * dw) (h * dh)) - SlideF (Rect dx dy dw dh) (True, Rect {..}) -> - (False, Rect (x + dy * w) (y + dx * h) (w * dh) (h * dw)) - SpinF (_, Rect {..}) -> (True, Rect x y w h) - StartingPointF r -> (False, bimap fromIntegral fromIntegral r) +toScreenCoord = + bimap floor ceiling . snd . cata \case + SlideF (Rect dx dy dw dh) (False, Rect {..}) -> + (False, Rect (x + dx * w) (y + dy * h) (w * dw) (h * dh)) + SlideF (Rect dx dy dw dh) (True, Rect {..}) -> + (False, Rect (x + dy * w) (y + dx * h) (w * dh) (h * dw)) + SpinF (_, Rect {..}) -> (True, Rect x y w h) + StartingPointF r -> (False, bimap fromIntegral fromIntegral r) --- |Extracts the original untransformed rectangle. +-- | Extracts the original untransformed rectangle. getStartingPoint :: Transformation -> XRect getStartingPoint = cata \case StartingPointF r -> r SpinF r -> r SlideF _ r -> r - diff --git a/src/TH.hs b/src/TH.hs index dc1732f..5241c98 100644 --- a/src/TH.hs +++ b/src/TH.hs @@ -1,119 +1,119 @@ -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE ViewPatterns #-} module TH where -import Language.Haskell.TH -import Standard hiding (init, input, output, state) -import Prelude (init) +import Language.Haskell.TH +import Standard hiding (init, input, output, state) +import Prelude (init) --- |Generates Input, Output, and State derivations for some monad, state s, and --- base newtype. +-- | Generates Input, Output, and State derivations for some monad, state s, and +-- base newtype. generateIOS :: Name -> Name -> TypeQ -> Q [Dec] generateIOS monad s viaType = do - input <- [d| deriving via $viaType instance HasSource $(conT s) $(conT s) $(conT monad) |] - output <- [d| deriving via $viaType instance HasSink $(conT s) $(conT s) $(conT monad) |] - state <- [d| deriving via $viaType instance HasState $(conT s) $(conT s) $(conT monad) |] + input <- [d|deriving via $viaType instance HasSource $(conT s) $(conT s) $(conT monad)|] + output <- [d|deriving via $viaType instance HasSink $(conT s) $(conT s) $(conT monad)|] + state <- [d|deriving via $viaType instance HasState $(conT s) $(conT s) $(conT monad)|] return $ join [input, output, state] --- |This piece of template haskell creates a series of --- patterns based on a given data type. +-- | This piece of template haskell creates a series of +-- patterns based on a given data type. -- --- Why? I decided typing Fix and unfix everywhere was annoying --- and I wanted to have smart constructors/deconstructors to --- handle everything for me. +-- Why? I decided typing Fix and unfix everywhere was annoying +-- and I wanted to have smart constructors/deconstructors to +-- handle everything for me. -- --- Originally I wanted to use Coercible to transform between the --- Fixed and unfixed versions but that didn't work. It seemed like --- Coercible didn't like the Wrap constructor for reasons I don't --- fully understand. +-- Originally I wanted to use Coercible to transform between the +-- Fixed and unfixed versions but that didn't work. It seemed like +-- Coercible didn't like the Wrap constructor for reasons I don't +-- fully understand. -- --- Instead, I created a new type class which had to be passed to --- this function. I have no idea if this template haskell is useful --- for any other datatypes or type classes. Each of those Name --- parameters correspond to either the datatype of one of the --- class details. Specifically, we need the class name, the associated --- type family, and the to/from functions inside the class. +-- Instead, I created a new type class which had to be passed to +-- this function. I have no idea if this template haskell is useful +-- for any other datatypes or type classes. Each of those Name +-- parameters correspond to either the datatype of one of the +-- class details. Specifically, we need the class name, the associated +-- type family, and the to/from functions inside the class. -- --- I was originally using quasiquoting, but things got weird in --- the type signatures and pattern names so I just made the AST --- manually. Interestingly enough, the docs around the AST are --- pretty great. +-- I was originally using quasiquoting, but things got weird in +-- the type signatures and pattern names so I just made the AST +-- manually. Interestingly enough, the docs around the AST are +-- pretty great. makeSimpleBase :: Name -> Name -> Name -> Name -> Name -> Q [Dec] makeSimpleBase name className tfName toName fromName = do -- Match on the datatype to get its constructors (cons') and -- type variables (aType). TyConI (DataD _ _ [KindedTV aType _] _ cons' _) <- reify name join <$> traverse (newDec aType) cons' - - where - newDec - :: Name -- ^The type variable name for the content of the container - -> Con -- ^A constructor - -> Q [Dec] -- ^The patterns - -- cName = the contsructor name - -- types = the list of parameter types for the contsructor - newDec aType (NormalC cName types) = do + where + newDec :: + -- | The type variable name for the content of the container + Name -> + -- | A constructor + Con -> + -- | The patterns + -- cName = the contsructor name + -- types = the list of parameter types for the contsructor + Q [Dec] + newDec aType (NormalC cName types) = do -- Given the list of types this constructor -- takes as a parameter, turn them into unique names. - binds <- typeListToBinds $ map snd types - -- get the old and new names to use in the signatures - let oldName = nameBase cName - let newerName = - mkName $ init oldName - -- A random other type we use in the pattern - let bType = mkName "b" - -- The type corresponding to the class name we were passed in - let classType = ConT className - -- The type signature for the pattern - -- See the docs for TH to get more info. - -- Corresponds to: - -- forall a b. (ClassType b, a ~ TfName b) => a -> a1 -> ... -> b - let patSigRHS = - ForallT - [ PlainTV aType, PlainTV bType ] - [ AppT classType $ VarT bType - , AppT (AppT EqualityT $ VarT aType) - $ AppT (ConT tfName) (VarT bType) + binds <- typeListToBinds $ map snd types + -- get the old and new names to use in the signatures + let oldName = nameBase cName + let newerName = + mkName $ init oldName + -- A random other type we use in the pattern + let bType = mkName "b" + -- The type corresponding to the class name we were passed in + let classType = ConT className + -- The type signature for the pattern + -- See the docs for TH to get more info. + -- Corresponds to: + -- forall a b. (ClassType b, a ~ TfName b) => a -> a1 -> ... -> b + let patSigRHS = + ForallT + [PlainTV aType, PlainTV bType] + [ AppT classType $ VarT bType, + AppT (AppT EqualityT $ VarT aType) $ + AppT (ConT tfName) (VarT bType) ] - $ foldr mkArrowType (VarT bType) - $ map snd types - -- Equivalent to: - -- pattern NewerName :: - let patSig = PatSynSigD newerName patSigRHS - - -- The right hand side of the function. Equivalent to: - -- (toName -> CName a a1 a2 a3 ... an) - patRHS <- - [p| ($(varE toName) -> $(conP cName $ map (return . VarP) binds)) |] - -- The function given by the fromName - let fromNameFunc = VarE fromName - -- The inmplementation of the bidirectional pattern. Equivalent to: - -- pattern NewerName a a1 ... an <- (toName -> CName a a1 ... an) where - -- NewerName a a1 ... an = fromName $ CName a a1 ... an - let pat = PatSynD - newerName - (PrefixPatSyn binds) - (ExplBidir - [ Clause - (map VarP binds) - (NormalB $ AppE fromNameFunc $ applyAll (ConE cName) binds) - [] - ] - ) - patRHS - - - return [patSig, pat] + $ foldr mkArrowType (VarT bType) $ + map snd types + -- Equivalent to: + -- pattern NewerName :: + let patSig = PatSynSigD newerName patSigRHS - newDec _ _ = error "Unable to build for this type" - mkArrowType :: Type -> Type -> Type - mkArrowType t1 t2 = AppT (AppT ArrowT t1) t2 - applyAll :: Exp -> [Name] -> Exp - applyAll = foldl' (\en n -> AppE en $ VarE n) - typeListToBinds :: [a] -> Q [Name] - typeListToBinds = traverse (\_ -> newName "c") + -- The right hand side of the function. Equivalent to: + -- (toName -> CName a a1 a2 a3 ... an) + patRHS <- + [p|($(varE toName) -> $(conP cName $ map (return . VarP) binds))|] + -- The function given by the fromName + let fromNameFunc = VarE fromName + -- The inmplementation of the bidirectional pattern. Equivalent to: + -- pattern NewerName a a1 ... an <- (toName -> CName a a1 ... an) where + -- NewerName a a1 ... an = fromName $ CName a a1 ... an + let pat = + PatSynD + newerName + (PrefixPatSyn binds) + ( ExplBidir + [ Clause + (map VarP binds) + (NormalB $ AppE fromNameFunc $ applyAll (ConE cName) binds) + [] + ] + ) + patRHS + return [patSig, pat] + newDec _ _ = error "Unable to build for this type" + mkArrowType :: Type -> Type -> Type + mkArrowType t1 t2 = AppT (AppT ArrowT t1) t2 + applyAll :: Exp -> [Name] -> Exp + applyAll = foldl' (\en n -> AppE en $ VarE n) + typeListToBinds :: [a] -> Q [Name] + typeListToBinds = traverse (\_ -> newName "c") diff --git a/src/Tiler/ManyHelpers.hs b/src/Tiler/ManyHelpers.hs index 26c9b34..0ee7180 100644 --- a/src/Tiler/ManyHelpers.hs +++ b/src/Tiler/ManyHelpers.hs @@ -2,78 +2,79 @@ module Tiler.ManyHelpers where -import Text.Show.Deriving -import Data.Eq.Deriving -import Standard -import FocusList -import Tiler.WithRect -import Tiler.Sized +import Data.Eq.Deriving +import FocusList +import Standard +import Text.Show.Deriving +import Tiler.Sized +import Tiler.WithRect - --- |A data type which holds the various options that the Many Tiler can be. +-- | A data type which holds the various options that the Many Tiler can be. data ManyHolder a where Horiz :: FocusedList (Sized a) -> ManyHolder a Floating :: FocusedList (WithRect a) -> ManyHolder a TwoCols :: Double -> FocusedList (Identity a) -> ManyHolder a deriving stock (Foldable, Functor, Traversable, Show, Eq) + deriveShow1 ''ManyHolder deriveEq1 ''ManyHolder - --- |Given a ManyHolder we don't want to pattern match against, apply some mapping. --- TODO This looks like a job for lenses. -withFl :: (Functor m) - => ManyHolder a - -> (forall f. (Traversable f, Comonad f) => FocusedList (f a) -> m (FocusedList (f b))) - -> m (ManyHolder b) +-- | Given a ManyHolder we don't want to pattern match against, apply some mapping. +-- TODO This looks like a job for lenses. +withFl :: + (Functor m) => + ManyHolder a -> + (forall f. (Traversable f, Comonad f) => FocusedList (f a) -> m (FocusedList (f b))) -> + m (ManyHolder b) withFl (Horiz fl) f = Horiz <$> f fl withFl (Floating fl) f = Floating <$> f fl withFl (TwoCols d fl) f = TwoCols d <$> f fl - --- |Nearly identical to withFl, but specialized for Identity to cut down on boilerplate. --- TODO it would be cool if there was something I could toss at this so that it --- didn't have to be it's own function but also didn't need the annoying --- runIdentity's everywhere. -withFl' :: ManyHolder a - -> (forall f. (Traversable f, Comonad f) => FocusedList (f a) -> FocusedList (f b)) - -> ManyHolder b +-- | Nearly identical to withFl, but specialized for Identity to cut down on boilerplate. +-- TODO it would be cool if there was something I could toss at this so that it +-- didn't have to be it's own function but also didn't need the annoying +-- runIdentity's everywhere. +withFl' :: + ManyHolder a -> + (forall f. (Traversable f, Comonad f) => FocusedList (f a) -> FocusedList (f b)) -> + ManyHolder b withFl' mh f = runIdentity $ withFl mh (Identity . f) - --- |Like the above except you can do whatever you want to the FocusedList. The --- downside is you can't recreate the ManyHolder afterwards. --- TODO This really looks like a job for lenses. Unfortunately, I run into --- problems with Impredicative Types when I try to do that. How can I make this --- a lense without much extra boilderplate -foldFl :: ManyHolder a - -> (forall f. (Comonad f) => FocusedList (f a) -> b) - -> b +-- | Like the above except you can do whatever you want to the FocusedList. The +-- downside is you can't recreate the ManyHolder afterwards. +-- TODO This really looks like a job for lenses. Unfortunately, I run into +-- problems with Impredicative Types when I try to do that. How can I make this +-- a lense without much extra boilderplate +foldFl :: + ManyHolder a -> + (forall f. (Comonad f) => FocusedList (f a) -> b) -> + b foldFl (Horiz fl) f = f fl foldFl (Floating fl) f = f fl foldFl (TwoCols _ fl) f = f fl --- |Converts a holder of Floating things into one of horizontal things. +-- | Converts a holder of Floating things into one of horizontal things. toFloating :: ManyHolder a -> ManyHolder a toFloating (Horiz fl) = Floating $ map (WithRect (Rect (-1) (-1) (-1) (-1)) . extract) fl toFloating (TwoCols _ fl) = Floating $ map (WithRect (Rect (-1) (-1) (-1) (-1)) . extract) fl toFloating mh@(Floating _) = mh --- |Like the above but in reverse. +-- | Like the above but in reverse. toHoriz :: ManyHolder a -> ManyHolder a -toHoriz (Floating fl) = Horiz $ map (Sized (1/len) . extract) fl - where len = fromIntegral $ flLength fl -toHoriz (TwoCols _ fl) = Horiz $ map (Sized (1/len) . extract) fl - where len = fromIntegral $ flLength fl +toHoriz (Floating fl) = Horiz $ map (Sized (1 / len) . extract) fl + where + len = fromIntegral $ flLength fl +toHoriz (TwoCols _ fl) = Horiz $ map (Sized (1 / len) . extract) fl + where + len = fromIntegral $ flLength fl toHoriz mh@(Horiz _) = mh --- |Like the above but in reverse. +-- | Like the above but in reverse. toTwoCols :: ManyHolder a -> ManyHolder a toTwoCols (Floating fl) = TwoCols 0.6 $ map (Identity . extract) fl toTwoCols (Horiz fl) = TwoCols 0.6 $ map (Identity . extract) fl toTwoCols mh@(TwoCols _ _) = mh - --- |Mods can be applied to any ManyHolder type. +-- | Mods can be applied to any ManyHolder type. data ManyMods = Rotate | Full | NoMods deriving stock (Show, Eq) diff --git a/src/Tiler/ParentChild.hs b/src/Tiler/ParentChild.hs index f62f546..6aebd36 100644 --- a/src/Tiler/ParentChild.hs +++ b/src/Tiler/ParentChild.hs @@ -1,18 +1,17 @@ module Tiler.ParentChild where -import Standard -import Graphics.X11.Types +import Graphics.X11.Types +import Standard --- |A child parent relationship between two windows. +-- | A child parent relationship between two windows. data ParentChild = ParentChild {getParent :: Window, getChild :: Window} - deriving Show + deriving (Show) --- |Is some window in the family? +-- | Is some window in the family? inParentChild :: Window -> ParentChild -> Bool inParentChild win (ParentChild ww ww') = win == ww || win == ww' --- |If either the child or the parent are equal, then the whole type --- is equal. TODO +-- | If either the child or the parent are equal, then the whole type +-- is equal. TODO instance Eq ParentChild where (ParentChild a b) == (ParentChild a' b') = a == a' || b == b' - diff --git a/src/Tiler/Sized.hs b/src/Tiler/Sized.hs index a4cbbca..c068dbc 100644 --- a/src/Tiler/Sized.hs +++ b/src/Tiler/Sized.hs @@ -2,13 +2,13 @@ module Tiler.Sized where -import Standard -import Text.Show.Deriving -import Data.Eq.Deriving +import Data.Eq.Deriving +import Standard +import Text.Show.Deriving --- |The sized datatype stores some element a and its --- size relative to something external. -data Sized a = Sized { getSize :: Double, getItem :: a } +-- | The sized datatype stores some element a and its +-- size relative to something external. +data Sized a = Sized {getSize :: Double, getItem :: a} deriving stock (Show, Functor, Foldable, Traversable, Generic) deriveShow1 ''Sized @@ -19,7 +19,7 @@ instance Applicative Sized where pure = Sized 0 Sized _ f <*> Sized s a = Sized s $ f a --- |The size is irrelevant when checking for equality. +-- | The size is irrelevant when checking for equality. instance Eq a => Eq (Sized a) where (Sized _ a) == (Sized _ b) = a == b diff --git a/src/Tiler/TilerTypes.hs b/src/Tiler/TilerTypes.hs index 2f4aa5b..03df7e8 100644 --- a/src/Tiler/TilerTypes.hs +++ b/src/Tiler/TilerTypes.hs @@ -1,29 +1,26 @@ {-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE ViewPatterns #-} -{-| - TilerF and its associated types are probably some of the most important in - the project. Understanding them is important to understanding how Xest works - as a whole. If you're unfamiliar with recursion schemes, definitely read up - on those. Learning about that library and reading - https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html - has probably changed how I write and think about code more than most of the - other concepts used in this project. --} +-- | +-- TilerF and its associated types are probably some of the most important in +-- the project. Understanding them is important to understanding how Xest works +-- as a whole. If you're unfamiliar with recursion schemes, definitely read up +-- on those. Learning about that library and reading +-- https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html +-- has probably changed how I write and think about code more than most of the +-- other concepts used in this project. module Tiler.TilerTypes where -import Standard -import Data.Functor.Foldable (embed) -import Tiler.ParentChild -import Tiler.ManyHelpers -import Text.Show.Deriving -import Data.Eq.Deriving -import Data.Kind -import TH +import Data.Eq.Deriving +import Data.Functor.Foldable (embed) +import Data.Kind import qualified SDL - - +import Standard +import TH +import Text.Show.Deriving +import Tiler.ManyHelpers +import Tiler.ParentChild type Borders = (SDL.Window, SDL.Window, SDL.Window, SDL.Window) @@ -31,27 +28,27 @@ type Borders = (SDL.Window, SDL.Window, SDL.Window, SDL.Window) -- constructor provides a way of composing windows. The a type variable is -- there so we can use recursion schemes on this data type. Every use of a can -- be thought of as a branch in the tree. -data TilerF a = - -- |Holds many elements. The instance of ManyHolder and the chosen Mods - -- decides how this one works. The ManyHolder options are to tile - -- horizontally or floating. The mods let you rotate the tiling (aka make a - -- vertical tiler) or make the focused Tiler full screen. +data TilerF a + = -- | Holds many elements. The instance of ManyHolder and the chosen Mods + -- decides how this one works. The ManyHolder options are to tile + -- horizontally or floating. The mods let you rotate the tiling (aka make a + -- vertical tiler) or make the focused Tiler full screen. ManyF (ManyHolder a) ManyMods - -- |The leaf of our Tiler tree. A WrapF holds a window (specifically a window - -- and its parent) and does nothing else. - | WrapF ParentChild - -- |This data type controls where Actions and XEvents happen. For example, - -- when a new window gets created, it gets placed as a child of whatever - -- comes immediately after the InputController. Unlike most other Tilers, - -- this one can be empty. - | InputControllerF Borders (Maybe a) - -- |Monitor decides where to start rendering. Only children of Monitor get - -- rendered. Just like InputController, Monitor can be empty. The list of - -- windows in Monitor are the list of "unmanaged" windows in that they - -- don't exist in the tree. - | MonitorF XRect (Maybe a) - + | -- | The leaf of our Tiler tree. A WrapF holds a window (specifically a window + -- and its parent) and does nothing else. + WrapF ParentChild + | -- | This data type controls where Actions and XEvents happen. For example, + -- when a new window gets created, it gets placed as a child of whatever + -- comes immediately after the InputController. Unlike most other Tilers, + -- this one can be empty. + InputControllerF Borders (Maybe a) + | -- | Monitor decides where to start rendering. Only children of Monitor get + -- rendered. Just like InputController, Monitor can be empty. The list of + -- windows in Monitor are the list of "unmanaged" windows in that they + -- don't exist in the tree. + MonitorF XRect (Maybe a) deriving stock (Eq, Show, Functor, Foldable, Traversable, Generic) + deriveShow1 ''TilerF deriveEq1 ''TilerF @@ -63,24 +60,24 @@ instance Recursive (TilerF (Fix TilerF)) where instance Corecursive (TilerF (Fix TilerF)) where embed = coerce --- |Used to make type signatures easier to read. A SubTiler is a Tiler that --- will be a child of another. +-- | Used to make type signatures easier to read. A SubTiler is a Tiler that +-- will be a child of another. type SubTiler = Fix TilerF --- |Another type signature helper. This type represents your standard, --- recursive Tiler. +-- | Another type signature helper. This type represents your standard, +-- recursive Tiler. type Tiler = TilerF (Fix TilerF) --- |This class represents things that can be transformed to and from --- the TilerF data type. It exists so we can ignore whether something actually --- needs to be Fixed. +-- | This class represents things that can be transformed to and from +-- the TilerF data type. It exists so we can ignore whether something actually +-- needs to be Fixed. class TilerLike a where type PolyA a :: Type toFType :: a -> TilerF (PolyA a) fromFType :: TilerF (PolyA a) -> a --- |A trivial instance. TilerF can be transformed into itself. +-- | A trivial instance. TilerF can be transformed into itself. instance TilerLike (TilerF a) where type PolyA (TilerF a) = a @@ -88,8 +85,8 @@ instance TilerLike (TilerF a) where fromFType = id --- |The interesting instance. A Fix TilerF can be coerced to and from --- TilerF. +-- | The interesting instance. A Fix TilerF can be coerced to and from +-- TilerF. instance TilerLike (Fix TilerF) where type PolyA (Fix TilerF) = Fix TilerF @@ -97,39 +94,42 @@ instance TilerLike (Fix TilerF) where fromFType = coerce --- |Generate some smart patterns that can match both TilerF and (Fix TilerF). +-- | Generate some smart patterns that can match both TilerF and (Fix TilerF). makeSimpleBase ''TilerF ''TilerLike ''PolyA 'toFType 'fromFType --- |Used to match either an InputController of a Monitor. You should probably --- use the pattern instead. +-- | Used to match either an InputController of a Monitor. You should probably +-- use the pattern instead. inputControllerOrMonitor :: TilerF a -> Maybe (Maybe b -> TilerF b, Maybe a) inputControllerOrMonitor (InputController bords a) = Just (InputController bords, a) inputControllerOrMonitor (Monitor loc a) = Just (Monitor loc, a) inputControllerOrMonitor _ = Nothing --- |The pattern used to match the function from above. -pattern InputControllerOrMonitor :: forall a b. - (Maybe b -> TilerF b) -> Maybe a -> TilerF a -pattern InputControllerOrMonitor c a - <- (inputControllerOrMonitor -> Just (c, a)) +-- | The pattern used to match the function from above. +pattern InputControllerOrMonitor :: + forall a b. + (Maybe b -> TilerF b) -> + Maybe a -> + TilerF a +pattern InputControllerOrMonitor c a <- + (inputControllerOrMonitor -> Just (c, a)) -- Since we're using pattern synonyms, Haskell can't figure out if any given -- case expression is total. This tells Haskell which pattern synonyms are -- needed to make it total. {-# COMPLETE Many, Wrap, InputControllerOrMonitor :: TilerF #-} -{-# COMPLETE Many, Wrap, Monitor, InputController :: TilerF #-} +{-# COMPLETE Many, Wrap, Monitor, InputController :: TilerF #-} -- These aren't super important and just make some type signatures nicer. --- |A function which can make any Subtiler, even those that don't exist, into a --- real Tiler. +-- | A function which can make any Subtiler, even those that don't exist, into a +-- real Tiler. type Reparenter = Maybe SubTiler -> Tiler --- |A Tiler that no longer sits in the tree. As a result of being removed from --- the tree, it could have been reduced to nothing. +-- | A Tiler that no longer sits in the tree. As a result of being removed from +-- the tree, it could have been reduced to nothing. type Unparented = Maybe Tiler --- |Each screen has an index and an associated Tiler. +-- | Each screen has an index and an associated Tiler. type Screens = IntMap Tiler diff --git a/src/Tiler/TreeCombo.hs b/src/Tiler/TreeCombo.hs index ace5558..aefb805 100644 --- a/src/Tiler/TreeCombo.hs +++ b/src/Tiler/TreeCombo.hs @@ -1,8 +1,8 @@ module Tiler.TreeCombo where -import Standard -import Tiler.TilerTypes -import Prelude (show) +import Standard +import Tiler.TilerTypes +import Prelude (show) -- | TreeCombo is used when trying to find the deepest common parent of two -- elements. One of the elements is considered unmovable. The other is movable @@ -19,17 +19,17 @@ import Prelude (show) -- -- A type of TreeCombo starts as Neither and moves towards both. You should not -- move back down the tree though. I think this forms a Monoid. -data TreeCombo = - -- |There's nothing special about this TreeCombo... yet. +data TreeCombo + = -- | There's nothing special about this TreeCombo... yet. Neither - -- |We've found the thing we're looking for which should not be moved. - | Unmovable - -- | We found the thing that can move. The Unparented is the children of + | -- | We've found the thing we're looking for which should not be moved. + Unmovable + | -- | We found the thing that can move. The Unparented is the children of -- what we found and Reparenter is a function to add the thing back into -- the tree. - | Movable (Reparenter, Unparented) - -- | We've already found both of the things we're looking for. - | Both + Movable (Reparenter, Unparented) + | -- | We've already found both of the things we're looking for. + Both -- Gets the Movable parameters if the TreeCombo is Movable getMovable :: TreeCombo -> Maybe (Reparenter, Unparented) diff --git a/src/Tiler/WithRect.hs b/src/Tiler/WithRect.hs index f0a7246..cfe4a13 100644 --- a/src/Tiler/WithRect.hs +++ b/src/Tiler/WithRect.hs @@ -2,12 +2,12 @@ module Tiler.WithRect where -import Standard -import Text.Show.Deriving -import Data.Eq.Deriving +import Data.Eq.Deriving +import Standard +import Text.Show.Deriving --- |A container where the contents are either on the bottom --- or are floating on top in some rectangle. +-- | A container where the contents are either on the bottom +-- or are floating on top in some rectangle. data WithRect a = WithRect Rect a deriving (Show, Functor, Foldable, Traversable) diff --git a/src/XEvents.hs b/src/XEvents.hs index 9883475..b709b62 100644 --- a/src/XEvents.hs +++ b/src/XEvents.hs @@ -1,29 +1,29 @@ {-# LANGUAGE StandaloneDeriving #-} - module XEvents where -import Standard -import Graphics.X11.Types -import Graphics.X11.Xinerama -import Graphics.X11.Xlib.Atom -import Data.Either ( ) -import Tiler.Tiler -import Base.DoAll -import Core -import FocusList -import qualified Data.Map.Strict as M -import qualified Data.IntMap as IM -import Graphics.X11.Xlib.Extras -import Config import Actions.ActionTypes -import Control.Monad.Trans.Maybe +import Base.DoAll +import Config import Control.Monad.Trans.Class - --- |Called when we want to reparent a window -reparentWin :: Members '[EventFlags, GlobalX, Log LogData, Property] m - => Window - -> m ParentChild +import Control.Monad.Trans.Maybe +import Core +import Data.Either () +import qualified Data.IntMap as IM +import qualified Data.Map.Strict as M +import FocusList +import Graphics.X11.Types +import Graphics.X11.Xinerama +import Graphics.X11.Xlib.Atom +import Graphics.X11.Xlib.Extras +import Standard +import Tiler.Tiler + +-- | Called when we want to reparent a window +reparentWin :: + Members '[EventFlags, GlobalX, Log LogData, Property] m => + Window -> + m ParentChild reparentWin window = do -- Reparent the window inside of a new one. -- Originally, Xest didn't do this but then a bunch of bugs came up @@ -40,71 +40,79 @@ reparentWin window = do deriving instance Show SizeHints --- |Called when a new top level window wants to exist -mapWin :: Members (Inputs '[Pointer, Screens]) m - => Members [EventFlags, GlobalX, Property, Log LogData, Mover] m - => Members (States [Screens, Tiler, Maybe (), ActiveScreen, Screens, LostWindow, OldTime, DockState]) m - => ParentChild - -> m () +-- | Called when a new top level window wants to exist +mapWin :: + Members (Inputs '[Pointer, Screens]) m => + Members [EventFlags, GlobalX, Property, Log LogData, Mover] m => + Members (States [Screens, Tiler, Maybe (), ActiveScreen, Screens, LostWindow, OldTime, DockState]) m => + ParentChild -> + m () mapWin pc@(ParentChild newWin window) = do log $ LD "MapWin" "Mapping a window" let tWin :: SubTiler = Wrap pc - void $ runMaybeT $ - do - parent <- MaybeT $ getTransientFor window - guard $ parent /= window - setScreenFromWindow parent - MaybeT do - root <- get @Tiler - log $ LD "MapWin" "Found a transient window!" - SizeHints{..} <- getSizeHints window - let idealSize = maybe (-1, -1) (over both fromIntegral) sh_min_size - let tilerParent = Wrap $ ParentChild parent parent - newRoot = foldMap1 (\f -> f idealSize tilerParent tWin root) - $ usingFloating :| [makeFloating] - extract $ - map (\t -> put @Tiler t >> newFocus newWin) newRoot <> - Succeeded (modify @LostWindow (M.insertWith (++) parent [pc])) - return $ Just () - <|> lift do - -- If a window wants to be transient for itself, just make it a normal window - modify @Tiler $ applyInput $ coerce $ \tiler -> map (add tWin) tiler <|> Just (coerce tWin) - newFocus newWin - -- Make the window full screen if needed - wm_state <- getAtom False "_NET_WM_STATE" - full_screen <- getAtom False "_NET_WM_STATE_FULLSCREEN" - isFullScreen <- (== Just full_screen) . headMay <$> getProperty 32 wm_state window - when isFullScreen $ - makeFullscreen window 1 - - -- Were any lost children expecting to find this window? - lostChildren <- view (at window) <$> get @LostWindow - traverse_ (traverse_ mapWin) lostChildren - - where + -- This code will try two paths (one for each side of the <|>). + -- If the "left" side fails, then that means we aren't a transient window. + -- The "right" side will always succeed. + void $ + runMaybeT $ + do + parent <- MaybeT $ getTransientFor window + guard $ parent /= window + setScreenFromWindow parent + MaybeT do + root <- get @Tiler + log $ LD "MapWin" "Found a transient window!" + SizeHints {..} <- getSizeHints window + let idealSize = maybe (-1, -1) (over both fromIntegral) sh_min_size + let tilerParent = Wrap $ ParentChild parent parent + newRoot = + foldMap1 (\f -> f idealSize tilerParent tWin root) $ + usingFloating :| [makeFloating] + extract $ + map (\t -> put @Tiler t >> newFocus newWin) newRoot + <> Succeeded (modify @LostWindow (M.insertWith (++) parent [pc])) + return $ Just () + <|> lift do + -- If a window wants to be transient for itself, just make it a normal window + modify @Tiler $ applyInput $ coerce $ \tiler -> map (add tWin) tiler <|> Just (coerce tWin) + newFocus newWin + -- Make the window full screen if needed + wm_state <- getAtom False "_NET_WM_STATE" + full_screen <- getAtom False "_NET_WM_STATE_FULLSCREEN" + isFullScreen <- (== Just full_screen) . headMay <$> getProperty 32 wm_state window + when isFullScreen $ + makeFullscreen window 1 + + -- Were any lost children expecting to find this window? + lostChildren <- view (at window) <$> get @LostWindow + traverse_ (traverse_ mapWin) lostChildren + where -- TODO Yikes to these two functions usingFloating :: (Double, Double) -> SubTiler -> SubTiler -> Tiler -> Tagged Tiler - usingFloating (newW, newH) t newTiler = coerce . cata \case - oldT@(Many (Floating fl) mods) -> - let bottom = fst $ pop (Left Front) fl - in if Failed t == extract bottom - then Succeeded $ Many (Floating $ push Back Focused (WithRect (Rect 0 0 newW newH) newTiler) $ map (map extract) fl) mods - else Fix <$> sequenceA oldT - oldT -> Fix <$> sequenceA oldT + usingFloating (newW, newH) t newTiler = + coerce . cata \case + oldT@(Many (Floating fl) mods) -> + let bottom = fst $ pop (Left Front) fl + in if Failed t == extract bottom + then Succeeded $ Many (Floating $ push Back Focused (WithRect (Rect 0 0 newW newH) newTiler) $ map (map extract) fl) mods + else Fix <$> sequenceA oldT + oldT -> Fix <$> sequenceA oldT makeFloating :: (Double, Double) -> SubTiler -> SubTiler -> Tiler -> Tagged Tiler - makeFloating (newW, newH) t newTiler = coerce . cata \oldTAndB -> - let oldT = map extract oldTAndB - in if oldT == unfix t - then Succeeded $ Many (Floating $ makeFL (WithRect (Rect (-1) (-1) (-1) (-1)) (Fix oldT) :| [WithRect (Rect 0 0 newW newH) newTiler]) 1) NoMods - else Fix <$> sequenceA oldTAndB - --- |A window was killed and no longer exists. Remove everything that --- was related to it. -killed :: Members (GlobalX ': States [Screens, LocCache, Maybe (), Docks]) m - => Window - -> m () + makeFloating (newW, newH) t newTiler = + coerce . cata \oldTAndB -> + let oldT = map extract oldTAndB + in if oldT == unfix t + then Succeeded $ Many (Floating $ makeFL (WithRect (Rect (-1) (-1) (-1) (-1)) (Fix oldT) :| [WithRect (Rect 0 0 newW newH) newTiler]) 1) NoMods + else Fix <$> sequenceA oldTAndB + +-- | A window was killed and no longer exists. Remove everything that +-- was related to it. +killed :: + Members (GlobalX ': States [Screens, LocCache, Maybe (), Docks]) m => + Window -> + m () killed window = do -- Find the parent in the tree and kill it. parentM <- asum . map (findParent window) <$> gets @Screens screensToTilers @@ -120,11 +128,12 @@ killed window = do -- Remove the window from the dock's cache. modify @Docks $ Docks . mfilter (/= window) . undock --- |A window is either dying slowly or has been minimized. -unmapWin :: Members (States [Screens, LocCache, Maybe (), Docks]) m - => Members [GlobalX, Property] m - => Window - -> m () +-- | A window is either dying slowly or has been minimized. +unmapWin :: + Members (States [Screens, LocCache, Maybe (), Docks]) m => + Members [GlobalX, Property] m => + Window -> + m () unmapWin window = do roots <- get @Screens @@ -135,25 +144,29 @@ unmapWin window = do moveToRoot window killed window --- |If we get a configure window event on the root, it probably means the user --- connected a new monitor or removed an old one. -rootChange :: Members '[Input [XineramaScreenInfo], Input NewBorders] m - => Members (States [Maybe (), Screens, ActiveScreen, [SubTiler], FocusedCache]) m - => m () +-- | If we get a configure window event on the root, it probably means the user +-- connected a new monitor or removed an old one. +rootChange :: + Members '[Input [XineramaScreenInfo], Input NewBorders] m => + Members (States [Maybe (), Screens, ActiveScreen, [SubTiler], FocusedCache]) m => + m () rootChange = do -- Update the list of screens screenInfo <- input @[XineramaScreenInfo] oldScreens <- get @Screens newScreens <- - IM.fromList <$> - traverse (\(XineramaScreenInfo name x y w h) -> do - NewBorders newBorders <- input @NewBorders - -- TODO This line is long... - let defaultTiler = Monitor (Rect (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h)) $ Just $ Fix $ InputController newBorders Nothing - return ( fromIntegral name - , IM.findWithDefault defaultTiler (fromIntegral name) $ oldScreens - ) - ) screenInfo + IM.fromList + <$> traverse + ( \(XineramaScreenInfo name x y w h) -> do + NewBorders newBorders <- input @NewBorders + -- TODO This line is long... + let defaultTiler = Monitor (Rect (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h)) $ Just $ Fix $ InputController newBorders Nothing + return + ( fromIntegral name, + IM.findWithDefault defaultTiler (fromIntegral name) $ oldScreens + ) + ) + screenInfo put @Screens newScreens -- Update the active screen if that monitor got disconnected @@ -167,14 +180,14 @@ rootChange = do -- Ask Xest to redraw and refocus put @FocusedCache $ FocusedCache 0 put @(Maybe ()) $ Just () - --- |Called when the mouse moves between windows or when the user --- clicks a window. -newFocus :: Members '[Input Screens, Property, Input Pointer, Log LogData] m - => Members (States [Screens, Tiler, Maybe (), ActiveScreen, Screens, OldTime]) m - => Window - -> m () +-- | Called when the mouse moves between windows or when the user +-- clicks a window. +newFocus :: + Members '[Input Screens, Property, Input Pointer, Log LogData] m => + Members (States [Screens, Tiler, Maybe (), ActiveScreen, Screens, OldTime]) m => + Window -> + m () newFocus window = do -- Change our tree so the focused window is the one we're hovering over -- It will get focused next time we redraw @@ -182,15 +195,15 @@ newFocus window = do modify @Tiler \tiler -> fromMaybe tiler $ focusWindow window tiler put @(Maybe ()) $ Just () - --- |On key press, execute some actions -keyDown :: Members '[Property, Executor] m - => Members (Inputs [Conf, Pointer, MouseButtons]) m - => Members (States [Tiler, Mode, KeyStatus, Maybe ()]) m - => Monoid (m ()) - => KeyCode - -> EventType - -> m [Action] +-- | On key press, execute some actions +keyDown :: + Members '[Property, Executor] m => + Members (Inputs [Conf, Pointer, MouseButtons]) m => + Members (States [Tiler, Mode, KeyStatus, Maybe ()]) m => + Monoid (m ()) => + KeyCode -> + EventType -> + m [Action] keyDown keycode eventType | eventType == keyPress = do put @(Maybe ()) $ Just () @@ -198,55 +211,58 @@ keyDown keycode eventType mode <- get @Mode -- Is keycode (the key that was pressed) equal to k (the bound key) case find (\(KeyTrigger k m _ _) -> keycode == k && m == mode) bindings of - Nothing -> return [] - Just (KeyTrigger _ _ actions newEa) -> do - -- KeyStatus is a state machine which decides if we - -- need to act like vim or notepad. - -- If the user holds down a key then clicks another, - -- act like notepad. If they press a key then release - -- it, act like vim. - modify @KeyStatus $ \case - Default -> Temp NotMod Default mode keycode newEa - ks@(New oldKS oldMode watchedKey ea) -> - if watchedKey == keycode - then ks - else Temp NotMod ( Temp FromMod oldKS oldMode watchedKey $ ChangeModeTo oldMode : ea) mode keycode newEa - ks@(Temp _ _ _ watchedKey _) -> - if watchedKey == keycode - then ks - else Temp NotMod ks mode keycode newEa - return actions - + Nothing -> return [] + Just (KeyTrigger _ _ actions newEa) -> do + -- KeyStatus is a state machine which decides if we + -- need to act like vim or notepad. + -- If the user holds down a key then clicks another, + -- act like notepad. If they press a key then release + -- it, act like vim. + modify @KeyStatus $ \case + Default -> Temp NotMod Default mode keycode newEa + ks@(New oldKS oldMode watchedKey ea) -> + if watchedKey == keycode + then ks + else Temp NotMod (Temp FromMod oldKS oldMode watchedKey $ ChangeModeTo oldMode : ea) mode keycode newEa + ks@(Temp _ _ _ watchedKey _) -> + if watchedKey == keycode + then ks + else Temp NotMod ks mode keycode newEa + return actions | otherwise = do put @(Maybe ()) $ Just () currentKS <- get @KeyStatus let (newKS_, actions) = (\(a, b) -> (a, b)) $ foldMap (\(a, b) -> (a, b)) $ para doRelease currentKS newKS_ return actions - where doRelease :: State KeyStatus m - => KeyStatusF (KeyStatus, Maybe (m (), [Action])) - -> Maybe (m (), [Action]) - doRelease = \case - NewF (_, otherActions) _ watchedKey _ -> - case otherActions of - Just _ -> Just (put @KeyStatus Default, []) - Nothing -> if watchedKey == keycode - then Just (put @KeyStatus Default, []) - else Nothing - TempF _ (oldKS, otherActions) _ watchedKey ea -> - case otherActions of - Just (cks, as) -> Just (put @KeyStatus oldKS >> cks, ea ++ as) - Nothing -> - if watchedKey == keycode - then Just (put @KeyStatus oldKS, ea) - else Nothing - DefaultF -> Nothing - --- |When the user moves the mouse in resize mode, this events are triggered. -motion :: Members '[Property] m - => Members (Inputs [Pointer, MouseButtons]) m - => Members (States [Tiler, OldMouseButtons, Maybe ()]) m - => m () + where + doRelease :: + State KeyStatus m => + KeyStatusF (KeyStatus, Maybe (m (), [Action])) -> + Maybe (m (), [Action]) + doRelease = \case + NewF (_, otherActions) _ watchedKey _ -> + case otherActions of + Just _ -> Just (put @KeyStatus Default, []) + Nothing -> + if watchedKey == keycode + then Just (put @KeyStatus Default, []) + else Nothing + TempF _ (oldKS, otherActions) _ watchedKey ea -> + case otherActions of + Just (cks, as) -> Just (put @KeyStatus oldKS >> cks, ea ++ as) + Nothing -> + if watchedKey == keycode + then Just (put @KeyStatus oldKS, ea) + else Nothing + DefaultF -> Nothing + +-- | When the user moves the mouse in resize mode, this events are triggered. +motion :: + Members '[Property] m => + Members (Inputs [Pointer, MouseButtons]) m => + Members (States [Tiler, OldMouseButtons, Maybe ()]) m => + m () motion = do -- First, let's find the current screen and its dimensions. Rect _ _ screenW screenH <- gets @Tiler getScreens @@ -255,8 +271,8 @@ motion = do case (getButtonLoc realButtonState, getButtonLoc lastButtonState) of (Just (xNow, yNow), Just (xLast, yLast)) -> do let direction = case realButtonState of - LeftButton _ -> Left - RightButton _ -> Right + LeftButton _ -> Left + RightButton _ -> Right change = direction (xNow - xLast, yNow - yLast) in do modify @Tiler $ applyInput $ map $ coerce (changeSize change (fromIntegral screenW, fromIntegral screenH)) @@ -265,9 +281,8 @@ motion = do input @MouseButtons >>= put @OldMouseButtons . OMB - --- |Helper function for motion. --- TODO This function probably belongs in Tiler. +-- | Helper function for motion. +-- TODO This function probably belongs in Tiler. changeSize :: Either (Int, Int) (Int, Int) -> (Int, Int) -> Tiler -> Tiler changeSize mouseLoc screen (Many mh mods) = flip Many mods case mh of @@ -278,30 +293,33 @@ changeSize mouseLoc screen (Many mh mods) = delta = direction $ fromEither mouseLoc screenSize = direction screen deltaPercent = delta / screenSize - focLoc = fromIntegral $ - (case mouseLoc of - Right _ -> id - Left _ -> (\n -> n-1) - ) $ findNeFocIndex fl - vList:: NonEmpty (Sized (Fix TilerF)) = vOrder fl - maxChange = getSize $ vList !! (focLoc+1) + focLoc = + fromIntegral $ + ( case mouseLoc of + Right _ -> id + Left _ -> (\n -> n -1) + ) + $ findNeFocIndex fl + vList :: NonEmpty (Sized (Fix TilerF)) = vOrder fl + maxChange = getSize $ vList !! (focLoc + 1) currentSize = getSize $ vList !! focLoc - bounded = max (0.01-currentSize) $ min maxChange deltaPercent + bounded = max (0.01 - currentSize) $ min maxChange deltaPercent withFocChange = over (ix focLoc) (\(Sized s a) -> Sized (s + bounded) a) vList - withPredChange = over (ix (focLoc+1)) (\(Sized s a) -> Sized (s - bounded) a) withFocChange - - in if focLoc > -1 && focLoc < length fl - 1 + withPredChange = over (ix (focLoc + 1)) (\(Sized s a) -> Sized (s - bounded) a) withFocChange + in if focLoc > -1 && focLoc < length fl - 1 then Horiz $ fromVis fl withPredChange else Horiz fl - Floating fl -> let (dx, dy) = bimap fromIntegral fromIntegral $ fromEither mouseLoc - in Floating $ mapOne (Right Focused) (\(WithRect Rect{..} t) -> - case mouseLoc of - Right _ -> WithRect (Rect x y (w + dx) (h + dy)) t - Left _ -> WithRect (Rect (x+dx) (y+dy) w h) t - ) fl - + in Floating $ + mapOne + (Right Focused) + ( \(WithRect Rect {..} t) -> + case mouseLoc of + Right _ -> WithRect (Rect x y (w + dx) (h + dy)) t + Left _ -> WithRect (Rect (x + dx) (y + dy) w h) t + ) + fl TwoCols colSize fl -> let direction = fromIntegral . if mods == Rotate then snd else fst delta = direction $ fromEither mouseLoc @@ -309,32 +327,37 @@ changeSize mouseLoc screen (Many mh mods) = deltaPercent = delta / screenSize newColSize = max 0 $ min 1 $ colSize + deltaPercent in TwoCols newColSize fl - changeSize _ _ t = t -makeFullscreen :: Members '[State Screens, State Tiler, Property, State ActiveScreen, State DockState, State (Maybe ()), Mover] m - => Window - -> Int - -> m () +makeFullscreen :: + Members '[State Screens, State Tiler, Property, State ActiveScreen, State DockState, State (Maybe ()), Mover] m => + Window -> + Int -> + m () makeFullscreen window isSet = do put @(Maybe ()) $ Just () runMaybeT $ setScreenFromWindow window -- Get the static parameters on Monitor and IC - loc <- gets @Tiler $ fromMaybe (error "Lost Mon") . cata \case - Monitor loc _ -> Just loc - t -> asum t - bords <- gets @Tiler $ fromMaybe (error "Lost Mon") . cata \case - InputController bords _ -> Just bords - t -> asum t + loc <- + gets @Tiler $ + fromMaybe (error "Lost Mon") . cata \case + Monitor loc _ -> Just loc + t -> asum t + bords <- + gets @Tiler $ + fromMaybe (error "Lost Mon") . cata \case + InputController bords _ -> Just bords + t -> asum t -- Get some useful atoms and window data wm_state <- getAtom False "_NET_WM_STATE" wm_full <- getAtom False "_NET_WM_STATE_FULLSCREEN" currentState <- getProperty 32 wm_state window - let shouldSet = if isSet == 2 - then not $ wm_full `elem` currentState - else isSet == 1 + let shouldSet = + if isSet == 2 + then not $ wm_full `elem` currentState + else isSet == 1 if shouldSet then do @@ -342,11 +365,12 @@ makeFullscreen window isSet = do -- Modify the tree with the newly zoomed in location root <- get @Tiler - modify @Tiler $ coerce . fromMaybe (coerce root) . cata \case - Wrap pc@(ParentChild _ child) - | child == window -> Just $ Monitor loc $ Just $ InputController bords $ Just $ Wrap pc - InputControllerOrMonitor _ t -> coerce $ join t - t -> coerce $ reduce t + modify @Tiler $ + coerce . fromMaybe (coerce root) . cata \case + Wrap pc@(ParentChild _ child) + | child == window -> Just $ Monitor loc $ Just $ InputController bords $ Just $ Wrap pc + InputControllerOrMonitor _ t -> coerce $ join t + t -> coerce $ reduce t -- Hide the docks put @DockState Hidden @@ -357,6 +381,6 @@ makeFullscreen window isSet = do setScreenFromWindow :: Members '[State Screens, State ActiveScreen] m => Window -> MaybeT m () setScreenFromWindow window = do - tilers <- lift $ gets @Screens $ zip [0..] . screensToTilers + tilers <- lift $ gets @Screens $ zip [0 ..] . screensToTilers (i, _) <- MaybeT $ return $ find snd $ map (second $ findWindow window) tilers lift $ put @ActiveScreen i