{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Layout.DecorationEx.Common -- Description : Declaration of types used by DecorationEx module, -- and commonly used utility functions. -- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov -- License : BSD-style (see xmonad/LICENSE) -- -- Maintainer : portnov84@rambler.ru -- Stability : unstable -- Portability : unportable -- -- This module exposes a number of types which are used by other sub-modules -- of "XMonad.Layout.DecorationEx" module. ----------------------------------------------------------------------------- module XMonad.Layout.DecorationEx.Common ( -- * Common types WindowDecoration (..) , WindowCommand (..) , DecorationWidget (..) , WidgetPlace (..) , WidgetLayout (..) , HasWidgets (..) , ClickHandler (..) , ThemeAttributes (..) , XPaintingContext , BoxBorders (..) , BorderColors , ThemeStyleType (..) , SimpleStyle (..) , GenericTheme (..) , ThemeEx -- * Utilities , widgetLayout , windowStyleType , genericWindowStyle , themeEx , borderColor , shadowBorder ) where import qualified Data.Map as M import Data.Bits (testBit) import XMonad import qualified XMonad.StackSet as W import XMonad.Hooks.UrgencyHook import qualified XMonad.Layout.Decoration as D -- | Information about decoration of one window data WindowDecoration = WindowDecoration { wdOrigWindow :: !Window -- ^ Original window (one being decorated) , wdOrigWinRect :: !Rectangle -- ^ Rectangle of original window , wdDecoWindow :: !(Maybe Window) -- ^ Decoration window, or Nothing if this window should not be decorated , wdDecoRect :: !(Maybe Rectangle) -- ^ Rectangle for decoration window , wdWidgets :: ![WidgetPlace] -- ^ Places for widgets } -- | Type class for window commands (such as maximize or close window) class (Read cmd, Show cmd) => WindowCommand cmd where -- | Execute the command executeWindowCommand :: cmd -> Window -> X Bool -- | Is the command currently in `checked' state. -- For example, for 'sticky' command, check if the -- window is currently sticky. isCommandChecked :: cmd -> Window -> X Bool -- | Type class for decoration widgets class (WindowCommand (WidgetCommand widget), Read widget, Show widget) => DecorationWidget widget where -- | Type of window commands which this type of widgets can execute type WidgetCommand widget -- | Get window command which is associated with this widget. widgetCommand :: widget -> Int -> WidgetCommand widget -- | Check if the widget is shrinkable, i.e. if it's width -- can be reduced if there is not enough place in the decoration. isShrinkable :: widget -> Bool -- | Layout of widgets data WidgetLayout a = WidgetLayout { wlLeft :: ![a] -- ^ Widgets that should be aligned to the left side of decoration , wlCenter :: ![a] -- ^ Widgets that should be in the center of decoration , wlRight :: ![a] -- ^ Widgets taht should be aligned to the right side of decoration } -- | Data type describing where the decoration widget (e.g. window button) -- should be placed. -- All coordinates are relative to decoration rectangle. data WidgetPlace = WidgetPlace { wpTextYPosition :: !Position -- ^ Y position of text base line -- (for widgets like window title or text-based buttons) , wpRectangle :: !Rectangle -- ^ Rectangle where to place the widget } deriving (Show) -- | Generic data type which is used to -- describe characteristics of rectangle borders. data BoxBorders a = BoxBorders { bxTop :: !a , bxRight :: !a , bxBottom :: !a , bxLeft :: !a } deriving (Eq, Read, Show) -- | Convinience data type describing colors of decoration rectangle borders. type BorderColors = BoxBorders String -- | Data type describing look of window decoration -- in particular state (active or inactive) data SimpleStyle = SimpleStyle { sBgColor :: !String -- ^ Decoration background color , sTextColor :: !String -- ^ Text (foreground) color , sTextBgColor :: !String -- ^ Text background color , sDecoBorderWidth :: !Dimension -- ^ Width of border of decoration rectangle. Set to 0 to disable the border. , sDecorationBorders :: !BorderColors -- ^ Colors of borders of decoration rectangle. } deriving (Show, Read) -- | Type class for themes, which claims that -- the theme contains the list of widgets and their alignments. class HasWidgets theme widget where themeWidgets :: theme widget -> WidgetLayout widget -- | Type class for themes, which claims that -- the theme can describe how the decoration should respond -- to clicks on decoration itself (between widgets). class ClickHandler theme widget where -- | This is called when the user clicks on the decoration rectangle -- (not on one of widgets). onDecorationClick :: theme widget -> Int -- ^ Mouse button number -> Maybe (WidgetCommand widget) -- | Determine if it is possible to drag window by it's decoration -- with mouse button. isDraggingEnabled :: theme widget -> Int -- ^ Mouse button number -> Bool -- | Type class for themes, which claims that the theme -- is responsible for determining looks of decoration. class (Read theme, Show theme) => ThemeAttributes theme where -- | Type which describes looks of decoration in one -- of window states (active, inactive, urgent, etc). type Style theme -- | Select style based on window state. selectWindowStyle :: theme -> Window -> X (Style theme) -- | Define padding between decoration rectangle and widgets. widgetsPadding :: theme -> BoxBorders Dimension -- | Initial background color of decoration rectangle. -- When decoration widget is created, it is initially filled -- with this color. defaultBgColor :: theme -> String -- | Font name defined in the theme. themeFontName :: theme -> String -- | Generic Theme data type. This is used -- by @TextEngine@ and can be used by other relatively -- simple decoration engines. data GenericTheme style widget = GenericTheme { exActive :: !style -- ^ Decoration style for active (focused) windows , exInactive :: !style -- ^ Decoration style for inactive (unfocused) windows , exUrgent :: !style -- ^ Decoration style for urgent windows , exPadding :: !(BoxBorders Dimension) -- ^ Padding between decoration rectangle and widgets , exFontName :: !String -- ^ Font name , exOnDecoClick :: !(M.Map Int (WidgetCommand widget)) -- ^ Correspondence between mouse button number and window command. , exDragWindowButtons :: ![Int] -- ^ For which mouse buttons dragging is enabled , exWidgetsLeft :: ![widget] -- ^ Widgets that should appear at the left of decoration rectangle (listed left to right) , exWidgetsCenter :: ![widget] -- ^ Widgets that should appear in the center of decoration rectangle (listed left to right) , exWidgetsRight :: ![widget] -- ^ Widgets that should appear at the right of decoration rectangle (listed left to right) } deriving instance (Show widget, Show (WidgetCommand widget), Show style) => Show (GenericTheme style widget) deriving instance (Read widget, Read (WidgetCommand widget), Read style) => Read (GenericTheme style widget) -- | Convience type for themes used by @TextDecoration@. type ThemeEx widget = GenericTheme SimpleStyle widget instance HasWidgets (GenericTheme style) widget where themeWidgets theme = WidgetLayout (exWidgetsLeft theme) (exWidgetsCenter theme) (exWidgetsRight theme) -- | Supported states of windows (on which looks of decorations can depend). data ThemeStyleType = ActiveWindow | UrgentWindow | InactiveWindow deriving (Eq, Show, Read) -- | Utility function to convert WidgetLayout to plain list of widgets. widgetLayout :: WidgetLayout widget -> [widget] widgetLayout ws = wlLeft ws ++ wlCenter ws ++ wlRight ws -- | Painting context for decoration engines based on plain X11 calls. type XPaintingContext = (Display, Pixmap, GC) instance (Show widget, Read widget, Read (WidgetCommand widget), Show (WidgetCommand widget)) => ThemeAttributes (ThemeEx widget) where type Style (ThemeEx widget) = SimpleStyle selectWindowStyle theme w = genericWindowStyle w theme defaultBgColor t = sBgColor $ exInactive t widgetsPadding = exPadding themeFontName = exFontName instance ClickHandler (GenericTheme SimpleStyle) widget where onDecorationClick theme button = M.lookup button (exOnDecoClick theme) isDraggingEnabled theme button = button `elem` exDragWindowButtons theme -- | Generic utility function to select style from @GenericTheme@ -- based on current state of the window. genericWindowStyle :: Window -> GenericTheme style widget -> X style genericWindowStyle win theme = do styleType <- windowStyleType win return $ case styleType of ActiveWindow -> exActive theme InactiveWindow -> exInactive theme UrgentWindow -> exUrgent theme -- | Detect type of style to be used from current state of the window. windowStyleType :: Window -> X ThemeStyleType windowStyleType win = do mbFocused <- W.peek <$> gets windowset isWmStateUrgent <- (win `elem`) <$> readUrgents isUrgencyBitSet <- withDisplay $ \dpy -> do hints <- io $ getWMHints dpy win return $ wmh_flags hints `testBit` urgencyHintBit if isWmStateUrgent || isUrgencyBitSet then return UrgentWindow else return $ case mbFocused of Nothing -> InactiveWindow Just focused | focused == win -> ActiveWindow | otherwise -> InactiveWindow -- | Convert Theme type from "XMonad.Layout.Decoration" to -- theme type used by "XMonad.Layout.DecorationEx.TextEngine". themeEx :: Default (WidgetCommand widget) => D.Theme -> ThemeEx widget themeEx t = GenericTheme { exActive = SimpleStyle (D.activeColor t) (D.activeTextColor t) (D.activeColor t) (D.activeBorderWidth t) (borderColor $ D.activeColor t) , exInactive = SimpleStyle (D.inactiveColor t) (D.inactiveTextColor t) (D.inactiveColor t) (D.inactiveBorderWidth t) (borderColor $ D.inactiveColor t) , exUrgent = SimpleStyle (D.urgentColor t) (D.urgentTextColor t) (D.urgentColor t) (D.urgentBorderWidth t) (borderColor $ D.urgentColor t) , exPadding = BoxBorders 0 4 0 4 , exFontName = D.fontName t , exOnDecoClick = M.fromList [(1, def)] , exDragWindowButtons = [1] , exWidgetsLeft = [] , exWidgetsCenter = [] , exWidgetsRight = [] } instance Default (WidgetCommand widget) => Default (ThemeEx widget) where def = themeEx (def :: D.Theme) borderColor :: String -> BorderColors borderColor c = BoxBorders c c c c shadowBorder :: String -> String -> BorderColors shadowBorder highlight shadow = BoxBorders highlight shadow shadow highlight