taffybar-5.2.0: src/System/Taffybar/Widget/Workspaces/EWMH.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StrictData #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- |
-- Module : System.Taffybar.Widget.Workspaces.EWMH
-- Copyright : (c) Ivan A. Malison
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Ivan A. Malison
-- Stability : unstable
-- Portability : unportable
module System.Taffybar.Widget.Workspaces.EWMH
( module System.Taffybar.Widget.Workspaces.EWMH,
module System.Taffybar.Widget.Workspaces.Shared,
module System.Taffybar.Widget.Workspaces.Config,
)
where
import Control.Arrow ((&&&))
import Control.Concurrent
import qualified Control.Concurrent.MVar as MV
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Control.RateLimit
import Data.Char (toLower)
import Data.Default (Default (..))
import qualified Data.Foldable as F
import Data.GI.Base.ManagedPtr (unsafeCastTo)
import Data.Int
import Data.List (elemIndex, intersect, isPrefixOf, sortBy, (\\))
import qualified Data.Map as M
import Data.Maybe
import qualified Data.MultiMap as MM
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Time.Units
import Data.Tuple.Select
import Data.Tuple.Sequence
import qualified GI.Gdk.Enums as Gdk
import qualified GI.Gdk.Structs.EventScroll as Gdk
import qualified GI.GdkPixbuf.Objects.Pixbuf as Gdk
import qualified GI.Gtk as Gtk
import System.Log.Logger
import System.Taffybar.Context
import System.Taffybar.Information.EWMHDesktopInfo
import System.Taffybar.Information.SafeX11
import System.Taffybar.Information.X11DesktopInfo
import System.Taffybar.Util
import System.Taffybar.Widget.Generic.ScalingImage (getScalingImageStrategy)
import System.Taffybar.Widget.Util
import System.Taffybar.Widget.Workspaces.Config
( WorkspaceWidgetCommonConfig (WorkspaceWidgetCommonConfig),
)
import qualified System.Taffybar.Widget.Workspaces.Config as WorkspaceWidgetConfig
import System.Taffybar.Widget.Workspaces.Shared
( WorkspaceState (..),
buildWorkspaceIconLabelOverlay,
cssWorkspaceStates,
getCSSClass,
mkWorkspaceIconWidget,
setWorkspaceWidgetStatusClass,
)
import System.Taffybar.WindowIcon
import Text.Printf
data WindowData = WindowData
{ windowId :: X11Window,
windowTitle :: String,
windowClass :: String,
windowUrgent :: Bool,
windowActive :: Bool,
windowMinimized :: Bool
}
deriving (Show, Eq)
data WidgetUpdate = WorkspaceUpdate Workspace | IconUpdate [X11Window]
data Workspace = Workspace
{ workspaceIdx :: WorkspaceId,
workspaceName :: String,
workspaceState :: WorkspaceState,
windows :: [WindowData]
}
deriving (Show, Eq)
data WorkspacesContext = WorkspacesContext
{ controllersVar :: MV.MVar (M.Map WorkspaceId WWC),
workspacesVar :: MV.MVar (M.Map WorkspaceId Workspace),
workspacesWidget :: Gtk.Box,
ewmhConfig :: WorkspacesConfig,
taffyContext :: Context
}
type WorkspacesIO a = ReaderT WorkspacesContext IO a
liftContext :: TaffyIO a -> WorkspacesIO a
liftContext action = asks taffyContext >>= lift . runReaderT action
liftX11Def :: a -> X11Property a -> WorkspacesIO a
liftX11Def dflt prop = liftContext $ runX11Def dflt prop
class WorkspaceWidgetController wc where
getWidget :: wc -> WorkspacesIO Gtk.Widget
updateWidget :: wc -> WidgetUpdate -> WorkspacesIO wc
updateWidgetX11 :: wc -> WidgetUpdate -> WorkspacesIO wc
updateWidgetX11 cont _ = return cont
data WWC = forall a. (WorkspaceWidgetController a) => WWC a
instance WorkspaceWidgetController WWC where
getWidget (WWC wc) = getWidget wc
updateWidget (WWC wc) update = WWC <$> updateWidget wc update
updateWidgetX11 (WWC wc) update = WWC <$> updateWidgetX11 wc update
type ControllerConstructor = Workspace -> WorkspacesIO WWC
type ParentControllerConstructor =
ControllerConstructor -> ControllerConstructor
type WindowIconPixbufGetter =
Int32 -> WindowData -> TaffyIO (Maybe Gdk.Pixbuf)
data WorkspacesConfig
= WorkspacesConfig
{ workspacesConfig ::
WorkspaceWidgetCommonConfig
(ReaderT WorkspacesContext IO)
Workspace
WindowData
WWC,
borderWidth :: Int,
updateEvents :: [String],
updateRateLimitMicroseconds :: Integer
}
workspacesCommonConfig ::
WorkspacesConfig ->
WorkspaceWidgetCommonConfig (ReaderT WorkspacesContext IO) Workspace WindowData WWC
workspacesCommonConfig = workspacesConfig
-- | Modify the nested 'WorkspaceWidgetCommonConfig' inside a 'WorkspacesConfig'.
--
-- Prefer this helper when overriding multiple fields to avoid accidentally
-- introducing a self-recursive config definition that can hang at runtime.
modifyCommonWorkspacesConfig ::
( WorkspaceWidgetCommonConfig (ReaderT WorkspacesContext IO) Workspace WindowData WWC ->
WorkspaceWidgetCommonConfig (ReaderT WorkspacesContext IO) Workspace WindowData WWC
) ->
WorkspacesConfig ->
WorkspacesConfig
modifyCommonWorkspacesConfig f cfg =
cfg {workspacesConfig = f (workspacesConfig cfg)}
-- | Replace the nested common config inside 'WorkspacesConfig'.
--
-- If you're just tweaking a few fields, prefer 'modifyCommonWorkspacesConfig'
-- to avoid accidentally creating a self-recursive definition that can hang at
-- runtime.
applyCommonWorkspacesConfig ::
WorkspaceWidgetCommonConfig (ReaderT WorkspacesContext IO) Workspace WindowData WWC ->
WorkspacesConfig ->
WorkspacesConfig
applyCommonWorkspacesConfig common cfg =
cfg {workspacesConfig = common}
defaultWorkspacesConfig :: WorkspacesConfig
defaultWorkspacesConfig =
WorkspacesConfig
{ workspacesConfig =
WorkspaceWidgetCommonConfig
{ WorkspaceWidgetConfig.widgetBuilder =
buildButtonController defaultBuildContentsController,
WorkspaceWidgetConfig.widgetGap = 0,
WorkspaceWidgetConfig.maxIcons = Nothing,
WorkspaceWidgetConfig.minIcons = 0,
WorkspaceWidgetConfig.getWindowIconPixbuf = defaultGetWindowIconPixbuf,
WorkspaceWidgetConfig.labelSetter = return . workspaceName,
WorkspaceWidgetConfig.showWorkspaceFn = \ws -> hideEmpty ws && hideSpecial ws,
WorkspaceWidgetConfig.iconSort = sortWindowsByPosition,
WorkspaceWidgetConfig.urgentWorkspaceState = False
},
borderWidth = 2,
updateEvents = allEWMHProperties \\ [ewmhWMIcon],
updateRateLimitMicroseconds = 100000
}
instance Default WorkspacesConfig where
def = defaultWorkspacesConfig
hideEmpty :: Workspace -> Bool
hideEmpty Workspace {workspaceState = Empty} = False
hideEmpty _ = True
hideSpecial :: Workspace -> Bool
hideSpecial Workspace {workspaceName = name} =
let lowered = map toLower name
in lowered /= "nsp" && not ("special:" `isPrefixOf` lowered)
wLog :: (MonadIO m) => Priority -> String -> m ()
wLog l s = liftIO $ logM "System.Taffybar.Widget.Workspaces" l s
updateVar :: MV.MVar a -> (a -> WorkspacesIO a) -> WorkspacesIO a
updateVar var modify = do
ctx <- ask
lift $ MV.modifyMVar var $ fmap (\a -> (a, a)) . flip runReaderT ctx . modify
updateWorkspacesVar :: WorkspacesIO (M.Map WorkspaceId Workspace)
updateWorkspacesVar = do
workspacesRef <- asks workspacesVar
updateVar workspacesRef buildWorkspaceData
getWorkspaceToWindows ::
[X11Window] -> X11Property (MM.MultiMap WorkspaceId X11Window)
getWorkspaceToWindows =
foldM
( \theMap window ->
MM.insert <$> getWorkspace window <*> pure window <*> pure theMap
)
MM.empty
getWindowData ::
Maybe X11Window ->
[X11Window] ->
X11Window ->
X11Property WindowData
getWindowData activeWindow urgentWindows window = do
wTitle <- getWindowTitle window
wClass <- getWindowClass window
wMinimized <- getWindowMinimized window
return
WindowData
{ windowId = window,
windowTitle = wTitle,
windowClass = wClass,
windowUrgent = window `elem` urgentWindows,
windowActive = Just window == activeWindow,
windowMinimized = wMinimized
}
buildWorkspaceData ::
M.Map WorkspaceId Workspace ->
WorkspacesIO (M.Map WorkspaceId Workspace)
buildWorkspaceData _ =
ask >>= \context -> liftX11Def M.empty $ do
names <- getWorkspaceNames
wins <- getWindows
workspaceToWindows <- getWorkspaceToWindows wins
urgentWindows <- filterM isWindowUrgent wins
activeWindow <- getActiveWindow
active : visible <- getVisibleWorkspaces
let common = workspacesCommonConfig (ewmhConfig context)
getWorkspaceState idx ws
| idx == active = Active
| idx `elem` visible = Visible
| WorkspaceWidgetConfig.urgentWorkspaceState common
&& not (null (ws `intersect` urgentWindows)) =
Urgent
| null ws = Empty
| otherwise = Hidden
foldM
( \theMap (idx, name) -> do
let ws = MM.lookup idx workspaceToWindows
windowInfos <- mapM (getWindowData activeWindow urgentWindows) ws
return $
M.insert
idx
Workspace
{ workspaceIdx = idx,
workspaceName = name,
workspaceState = getWorkspaceState idx ws,
windows = windowInfos
}
theMap
)
M.empty
names
addWidgetsToTopLevel :: WorkspacesIO ()
addWidgetsToTopLevel = do
WorkspacesContext
{ controllersVar = controllersRef,
workspacesWidget = cont
} <-
ask
controllersMap <- lift $ MV.readMVar controllersRef
-- Elems returns elements in ascending order of their keys so this will always
-- add the widgets in the correct order
mapM_ addWidget $ M.elems controllersMap
lift $ Gtk.widgetShowAll cont
addWidget :: WWC -> WorkspacesIO ()
addWidget controller = do
cont <- asks workspacesWidget
workspaceWidget <- getWidget controller
lift $ do
-- XXX: This hbox exists to (hopefully) prevent the issue where workspace
-- widgets appear out of order, in the switcher, by acting as an empty
-- place holder when the actual widget is hidden.
hbox <- Gtk.boxNew Gtk.OrientationHorizontal 0
void $
Gtk.widgetGetParent workspaceWidget
>>= traverse (unsafeCastTo Gtk.Box)
>>= traverse (`Gtk.containerRemove` workspaceWidget)
Gtk.containerAdd hbox workspaceWidget
Gtk.containerAdd cont hbox
workspacesNew :: WorkspacesConfig -> TaffyIO Gtk.Widget
workspacesNew cfg =
ask >>= \tContext -> lift $ do
let common = workspacesCommonConfig cfg
cont <- Gtk.boxNew Gtk.OrientationHorizontal $ fromIntegral (WorkspaceWidgetConfig.widgetGap common)
controllersRef <- MV.newMVar M.empty
workspacesRef <- MV.newMVar M.empty
let context =
WorkspacesContext
{ controllersVar = controllersRef,
workspacesVar = workspacesRef,
workspacesWidget = cont,
ewmhConfig = cfg,
taffyContext = tContext
}
-- This will actually create all the widgets
runReaderT updateAllWorkspaceWidgets context
updateHandler <- onWorkspaceUpdate context
iconHandler <- onIconsChanged context
let doUpdate = lift . updateHandler
handleConfigureEvents e@(ConfigureEvent {}) = doUpdate e
handleConfigureEvents _ = return ()
(workspaceSubscription, iconSubscription, geometrySubscription) <-
flip runReaderT tContext $
sequenceT
( subscribeToPropertyEvents (updateEvents cfg) doUpdate,
subscribeToPropertyEvents [ewmhWMIcon] (lift . onIconChanged iconHandler),
subscribeToAll handleConfigureEvents
)
let doUnsubscribe =
flip runReaderT tContext $
mapM_
unsubscribe
[ iconSubscription,
workspaceSubscription,
geometrySubscription
]
_ <- Gtk.onWidgetUnrealize cont doUnsubscribe
_ <- widgetSetClassGI cont "workspaces"
Gtk.toWidget cont
updateAllWorkspaceWidgets :: WorkspacesIO ()
updateAllWorkspaceWidgets = do
wLog DEBUG "Updating workspace widgets"
workspacesMap <- updateWorkspacesVar
wLog DEBUG $ printf "Workspaces: %s" $ show workspacesMap
wLog DEBUG "Adding and removing widgets"
updateWorkspaceControllers
let updateController' idx controller =
maybe
(return controller)
(updateWidget controller . WorkspaceUpdate)
$ M.lookup idx workspacesMap
logUpdateController i =
wLog DEBUG $ printf "Updating %s workspace widget" $ show i
updateController i cont =
logUpdateController i
>> updateController' i cont
wLog DEBUG "Done updating individual widget"
doWidgetUpdate updateController
wLog DEBUG "Showing and hiding controllers"
setControllerWidgetVisibility
setControllerWidgetVisibility :: WorkspacesIO ()
setControllerWidgetVisibility = do
ctx@WorkspacesContext
{ workspacesVar = workspacesRef,
controllersVar = controllersRef,
ewmhConfig = cfg
} <-
ask
let common = workspacesCommonConfig cfg
lift $ do
workspacesMap <- MV.readMVar workspacesRef
controllersMap <- MV.readMVar controllersRef
forM_ (M.elems workspacesMap) $ \ws ->
let action =
if WorkspaceWidgetConfig.showWorkspaceFn common ws
then Gtk.widgetShow
else Gtk.widgetHide
in traverse
(flip runReaderT ctx . getWidget)
(M.lookup (workspaceIdx ws) controllersMap)
>>= maybe (return ()) action
doWidgetUpdate :: (WorkspaceId -> WWC -> WorkspacesIO WWC) -> WorkspacesIO ()
doWidgetUpdate updateController = do
c@WorkspacesContext {controllersVar = controllersRef} <- ask
lift $ MV.modifyMVar_ controllersRef $ \controllers -> do
wLog DEBUG "Updating controllers ref"
controllersList <-
mapM
( \(idx, controller) -> do
newController <- runReaderT (updateController idx controller) c
return (idx, newController)
)
$ M.toList controllers
return $ M.fromList controllersList
updateWorkspaceControllers :: WorkspacesIO ()
updateWorkspaceControllers = do
WorkspacesContext
{ controllersVar = controllersRef,
workspacesVar = workspacesRef,
workspacesWidget = cont,
ewmhConfig = cfg
} <-
ask
workspacesMap <- lift $ MV.readMVar workspacesRef
controllersMap <- lift $ MV.readMVar controllersRef
let newWorkspacesSet = M.keysSet workspacesMap
existingWorkspacesSet = M.keysSet controllersMap
common = workspacesCommonConfig cfg
when (existingWorkspacesSet /= newWorkspacesSet) $ do
let addWorkspaces = Set.difference newWorkspacesSet existingWorkspacesSet
removeWorkspaces = Set.difference existingWorkspacesSet newWorkspacesSet
builder = WorkspaceWidgetConfig.widgetBuilder common
_ <- updateVar controllersRef $ \controllers -> do
let oldRemoved = F.foldl' (flip M.delete) controllers removeWorkspaces
buildController idx = builder <$> M.lookup idx workspacesMap
buildAndAddController theMap idx =
maybe
(return theMap)
(>>= return . flip (M.insert idx) theMap)
(buildController idx)
foldM buildAndAddController oldRemoved $ Set.toList addWorkspaces
-- Clear the container and repopulate it
lift $ Gtk.containerForeach cont (Gtk.containerRemove cont)
addWidgetsToTopLevel
rateLimitFn ::
forall req resp.
WorkspacesContext ->
(req -> IO resp) ->
ResultsCombiner req resp ->
IO (req -> IO resp)
rateLimitFn context =
let limit = (updateRateLimitMicroseconds $ ewmhConfig context)
rate = fromMicroseconds limit :: Microsecond
in generateRateLimitedFunction $ PerInvocation rate
onWorkspaceUpdate :: WorkspacesContext -> IO (Event -> IO ())
onWorkspaceUpdate context = do
rateLimited <- rateLimitFn context doUpdate combineRequests
let withLog event = do
case event of
PropertyEvent _ _ _ _ _ atom _ _ ->
wLog DEBUG $ printf "Event %s" $ show atom
_anythingElse -> return ()
void $ forkIO $ rateLimited event
return withLog
where
combineRequests _ b = Just (b, const ((), ()))
doUpdate _ = postGUIASync $ runReaderT updateAllWorkspaceWidgets context
onIconChanged :: (Set.Set X11Window -> IO ()) -> Event -> IO ()
onIconChanged handler event =
case event of
PropertyEvent {ev_window = wid} -> do
wLog DEBUG $ printf "Icon changed event %s" $ show wid
handler $ Set.singleton wid
_ -> return ()
onIconsChanged :: WorkspacesContext -> IO (Set.Set X11Window -> IO ())
onIconsChanged context = rateLimitFn context onIconsChanged' combineRequests
where
combineRequests windows1 windows2 =
Just (Set.union windows1 windows2, const ((), ()))
onIconsChanged' wids = do
wLog DEBUG $ printf "Icon update execute %s" $ show wids
postGUIASync $
flip runReaderT context $
doWidgetUpdate
( \idx c ->
wLog DEBUG (printf "Updating %s icons." $ show idx)
>> updateWidget c (IconUpdate $ Set.toList wids)
)
initializeWWC ::
(WorkspaceWidgetController a) => a -> Workspace -> ReaderT WorkspacesContext IO WWC
initializeWWC controller ws =
WWC <$> updateWidget controller (WorkspaceUpdate ws)
-- | A WrappingController can be used to wrap some child widget with another
-- abitrary widget.
data WrappingController = WrappingController
{ wrappedWidget :: Gtk.Widget,
wrappedController :: WWC
}
instance WorkspaceWidgetController WrappingController where
getWidget = lift . Gtk.toWidget . wrappedWidget
updateWidget wc update = do
updated <- updateWidget (wrappedController wc) update
return wc {wrappedController = updated}
data WorkspaceContentsController = WorkspaceContentsController
{ containerWidget :: Gtk.Widget,
contentsControllers :: [WWC]
}
buildContentsController :: [ControllerConstructor] -> ControllerConstructor
buildContentsController constructors ws = do
controllers <- mapM ($ ws) constructors
ctx <- ask
tempController <- lift $ do
cons <- Gtk.boxNew Gtk.OrientationHorizontal 0
mapM_ (flip runReaderT ctx . getWidget >=> Gtk.containerAdd cons) controllers
outerBox <- Gtk.toWidget cons >>= buildPadBox
_ <- widgetSetClassGI cons "contents"
widget <- Gtk.toWidget outerBox
return
WorkspaceContentsController
{ containerWidget = widget,
contentsControllers = controllers
}
initializeWWC tempController ws
defaultBuildContentsController :: ControllerConstructor
defaultBuildContentsController =
buildContentsController [buildLabelController, buildIconController]
bottomLeftAlignedBoxWrapper :: T.Text -> ControllerConstructor -> ControllerConstructor
bottomLeftAlignedBoxWrapper boxClass constructor ws = do
controller <- constructor ws
widget <- getWidget controller
wrapped <- lift $ buildBottomLeftAlignedBox boxClass widget
let wrappingController =
WrappingController
{ wrappedWidget = wrapped,
wrappedController = controller
}
initializeWWC wrappingController ws
buildLabelOverlayController :: ControllerConstructor
buildLabelOverlayController ws = do
iconController <- buildIconController ws
labelController <- buildLabelController ws
ctx <- ask
tempController <- lift $ do
iconWidget <- runReaderT (getWidget iconController) ctx
labelWidget <- runReaderT (getWidget labelController) ctx
widget <- buildWorkspaceIconLabelOverlay iconWidget labelWidget
return
WorkspaceContentsController
{ containerWidget = widget,
contentsControllers = [iconController, labelController]
}
initializeWWC tempController ws
buildOverlayContentsController ::
[ControllerConstructor] -> [ControllerConstructor] -> ControllerConstructor
buildOverlayContentsController mainConstructors overlayConstructors ws = do
controllers <- mapM ($ ws) mainConstructors
overlayControllers <- mapM ($ ws) overlayConstructors
ctx <- ask
tempController <- lift $ do
mainContents <- Gtk.boxNew Gtk.OrientationHorizontal 0
mapM_
(flip runReaderT ctx . getWidget >=> Gtk.containerAdd mainContents)
controllers
outerBox <- Gtk.toWidget mainContents >>= buildPadBox
_ <- widgetSetClassGI mainContents "contents"
overlayWidgets <- mapM (flip runReaderT ctx . getWidget) overlayControllers
widget <- buildOverlayWithPassThrough outerBox overlayWidgets
return
WorkspaceContentsController
{ containerWidget = widget,
contentsControllers = controllers ++ overlayControllers
}
initializeWWC tempController ws
instance WorkspaceWidgetController WorkspaceContentsController where
getWidget = return . containerWidget
updateWidget cc update = do
WorkspacesContext {} <- ask
case update of
WorkspaceUpdate newWorkspace ->
lift $ setWorkspaceWidgetStatusClass (workspaceState newWorkspace) $ containerWidget cc
_ -> return ()
newControllers <- mapM (`updateWidget` update) $ contentsControllers cc
return cc {contentsControllers = newControllers}
updateWidgetX11 cc update = do
newControllers <- mapM (`updateWidgetX11` update) $ contentsControllers cc
return cc {contentsControllers = newControllers}
newtype LabelController = LabelController {label :: Gtk.Label}
buildLabelController :: ControllerConstructor
buildLabelController ws = do
tempController <- lift $ do
lbl <- Gtk.labelNew Nothing
_ <- widgetSetClassGI lbl "workspace-label"
return LabelController {label = lbl}
initializeWWC tempController ws
instance WorkspaceWidgetController LabelController where
getWidget = lift . Gtk.toWidget . label
updateWidget lc (WorkspaceUpdate newWorkspace) = do
WorkspacesContext {ewmhConfig = cfg} <- ask
let common = workspacesCommonConfig cfg
labelText <- WorkspaceWidgetConfig.labelSetter common newWorkspace
lift $ do
Gtk.labelSetMarkup (label lc) $ T.pack labelText
setWorkspaceWidgetStatusClass (workspaceState newWorkspace) $ label lc
return lc
updateWidget lc _ = return lc
type IconWidget = WindowIconWidget WindowData
buildIconWidget :: Bool -> Workspace -> WorkspacesIO IconWidget
buildIconWidget transparentOnNone ws = do
ctx <- ask
let tContext = taffyContext ctx
cfg = ewmhConfig ctx
common = workspacesCommonConfig cfg
getPB size windowData =
runReaderT (WorkspaceWidgetConfig.getWindowIconPixbuf common size windowData) tContext
strategy <- liftContext getScalingImageStrategy
lift $ do
iconWidget <-
mkWorkspaceIconWidget
strategy
Nothing
transparentOnNone
getPB
(`pixBufFromColor` 0)
_ <-
Gtk.onWidgetButtonPressEvent (iconContainer iconWidget) $
const $
liftIO $ do
info <- MV.readMVar (iconWindow iconWidget)
case info of
Just updatedInfo ->
flip runReaderT ctx $
liftX11Def () $
focusWindow $
windowId updatedInfo
_ -> liftIO $ void $ switch ctx (workspaceIdx ws)
return True
return iconWidget
data IconController = IconController
{ iconsContainer :: Gtk.Box,
iconImages :: [IconWidget],
iconWorkspace :: Workspace
}
buildIconController :: ControllerConstructor
buildIconController ws = do
tempController <-
lift $ do
hbox <- Gtk.boxNew Gtk.OrientationHorizontal 0
return
IconController
{ iconsContainer = hbox,
iconImages = [],
iconWorkspace = ws
}
initializeWWC tempController ws
instance WorkspaceWidgetController IconController where
getWidget = lift . Gtk.toWidget . iconsContainer
updateWidget ic (WorkspaceUpdate newWorkspace) = do
newImages <- updateImages ic newWorkspace
return ic {iconImages = newImages, iconWorkspace = newWorkspace}
updateWidget ic (IconUpdate updatedIcons) =
updateWindowIconsById ic updatedIcons >> return ic
updateWindowIconsById ::
IconController -> [X11Window] -> WorkspacesIO ()
updateWindowIconsById ic windowIds =
mapM_ maybeUpdateWindowIcon $ iconImages ic
where
maybeUpdateWindowIcon widget =
do
info <- lift $ MV.readMVar $ iconWindow widget
when (maybe False (flip elem windowIds . windowId) info) $
updateIconWidget ic widget info
scaledWindowIconPixbufGetter :: WindowIconPixbufGetter -> WindowIconPixbufGetter
scaledWindowIconPixbufGetter = scaledPixbufGetter
constantScaleWindowIconPixbufGetter ::
Int32 -> WindowIconPixbufGetter -> WindowIconPixbufGetter
constantScaleWindowIconPixbufGetter constantSize getter =
const $ scaledWindowIconPixbufGetter getter constantSize
handleIconGetterException :: WindowIconPixbufGetter -> WindowIconPixbufGetter
handleIconGetterException = handlePixbufGetterException wLog
getWindowIconPixbufFromEWMH :: WindowIconPixbufGetter
getWindowIconPixbufFromEWMH = handleIconGetterException $ \size windowData ->
getCachedIconPixBufFromEWMH size (windowId windowData)
getWindowIconPixbufFromClass :: WindowIconPixbufGetter
getWindowIconPixbufFromClass = handleIconGetterException $ \size windowData ->
getCachedWindowIconFromClasses size (windowClass windowData)
getWindowIconPixbufFromDesktopEntry :: WindowIconPixbufGetter
getWindowIconPixbufFromDesktopEntry = handleIconGetterException $ \size windowData ->
getCachedWindowIconFromDesktopEntryByClasses size (windowClass windowData)
getWindowIconPixbufFromChrome :: WindowIconPixbufGetter
getWindowIconPixbufFromChrome _ windowData =
getPixBufFromChromeData $ windowId windowData
defaultGetWindowIconPixbuf :: WindowIconPixbufGetter
defaultGetWindowIconPixbuf =
scaledWindowIconPixbufGetter unscaledDefaultGetWindowIconPixbuf
unscaledDefaultGetWindowIconPixbuf :: WindowIconPixbufGetter
unscaledDefaultGetWindowIconPixbuf =
getWindowIconPixbufFromDesktopEntry
<|||> getWindowIconPixbufFromClass
<|||> getWindowIconPixbufFromEWMH
addCustomIconsToDefaultWithFallbackByPath ::
(WindowData -> Maybe FilePath) ->
FilePath ->
WindowIconPixbufGetter
addCustomIconsToDefaultWithFallbackByPath getCustomIconPath fallbackPath =
addCustomIconsAndFallback
getCustomIconPath
(const $ lift $ getPixbufFromFilePath fallbackPath)
unscaledDefaultGetWindowIconPixbuf
addCustomIconsAndFallback ::
(WindowData -> Maybe FilePath) ->
(Int32 -> TaffyIO (Maybe Gdk.Pixbuf)) ->
WindowIconPixbufGetter ->
WindowIconPixbufGetter
addCustomIconsAndFallback getCustomIconPath fallback defaultGetter =
scaledWindowIconPixbufGetter $
getCustomIcon <|||> defaultGetter <|||> (\s _ -> fallback s)
where
getCustomIcon :: Int32 -> WindowData -> TaffyIO (Maybe Gdk.Pixbuf)
getCustomIcon _ wdata =
lift $
maybe (return Nothing) getPixbufFromFilePath $
getCustomIconPath wdata
-- | Sort windows by top-left corner position.
sortWindowsByPosition :: [WindowData] -> WorkspacesIO [WindowData]
sortWindowsByPosition wins = do
let getGeometryWorkspaces w = getDisplay >>= liftIO . (`safeGetGeometry` w)
getGeometries =
mapM
( forkM
return
(((sel2 &&& sel3) <$>) . getGeometryWorkspaces)
. windowId
)
wins
windowGeometries <- liftX11Def [] getGeometries
let getLeftPos wd =
fromMaybe (999999999, 99999999) $ lookup (windowId wd) windowGeometries
compareWindowData a b =
compare
(windowMinimized a, getLeftPos a)
(windowMinimized b, getLeftPos b)
return $ sortBy compareWindowData wins
-- | Sort windows in reverse _NET_CLIENT_LIST_STACKING order.
-- Starting in xmonad-contrib 0.17.0, this is effectively focus history, active first.
-- Previous versions erroneously stored focus-sort-order in _NET_CLIENT_LIST.
sortWindowsByStackIndex :: [WindowData] -> WorkspacesIO [WindowData]
sortWindowsByStackIndex wins = do
stackingWindows <- liftX11Def [] getWindowsStacking
let getStackIdx wd = fromMaybe (-1) $ elemIndex (windowId wd) stackingWindows
compareWindowData a b = compare (getStackIdx b) (getStackIdx a)
return $ sortBy compareWindowData wins
updateImages :: IconController -> Workspace -> WorkspacesIO [IconWidget]
updateImages ic ws = do
WorkspacesContext {ewmhConfig = cfg} <- ask
let common = workspacesCommonConfig cfg
sortedWindows <- WorkspaceWidgetConfig.iconSort common $ windows ws
wLog DEBUG $ printf "Updating images for %s" (show ws)
let (effectiveMinIcons, _targetLen, paddedWindows) =
computeIconStripLayout
(WorkspaceWidgetConfig.minIcons common)
(WorkspaceWidgetConfig.maxIcons common)
sortedWindows
buildOne i = buildIconWidget (i < effectiveMinIcons) ws
updateOne = updateIconWidget ic
syncWidgetPool (iconsContainer ic) (iconImages ic) paddedWindows buildOne iconContainer updateOne
getWindowStatusString :: WindowData -> T.Text
getWindowStatusString windowData =
windowStatusClassFromFlags
(windowMinimized windowData)
(windowActive windowData)
(windowUrgent windowData)
updateIconWidget ::
IconController ->
IconWidget ->
Maybe WindowData ->
WorkspacesIO ()
updateIconWidget _ iconWidget windowData =
updateWindowIconWidgetState
iconWidget
windowData
(T.pack . windowTitle)
getWindowStatusString
data WorkspaceButtonController = WorkspaceButtonController
{ button :: Gtk.EventBox,
buttonWorkspace :: Workspace,
contentsController :: WWC
}
buildButtonController :: ParentControllerConstructor
buildButtonController contentsBuilder workspace = do
cc <- contentsBuilder workspace
workspacesRef <- asks workspacesVar
ctx <- ask
widget <- getWidget cc
lift $ do
ebox <- Gtk.eventBoxNew
Gtk.containerAdd ebox widget
Gtk.eventBoxSetVisibleWindow ebox False
_ <-
Gtk.onWidgetScrollEvent ebox $ \scrollEvent -> do
dir <- Gdk.getEventScrollDirection scrollEvent
workspaces <- liftIO $ MV.readMVar workspacesRef
let switchOne a =
liftIO $
flip runReaderT ctx $
liftX11Def
()
(switchOneWorkspace a (length (M.toList workspaces) - 1))
>> return True
case dir of
Gdk.ScrollDirectionUp -> switchOne True
Gdk.ScrollDirectionLeft -> switchOne True
Gdk.ScrollDirectionDown -> switchOne False
Gdk.ScrollDirectionRight -> switchOne False
_ -> return False
_ <- Gtk.onWidgetButtonPressEvent ebox $ const $ switch ctx $ workspaceIdx workspace
return $
WWC
WorkspaceButtonController
{ button = ebox,
buttonWorkspace = workspace,
contentsController = cc
}
switch :: (MonadIO m) => WorkspacesContext -> WorkspaceId -> m Bool
switch ctx idx = do
liftIO $ flip runReaderT ctx $ liftX11Def () $ switchToWorkspace idx
return True
instance WorkspaceWidgetController WorkspaceButtonController where
getWidget wbc = lift $ Gtk.toWidget $ button wbc
updateWidget wbc update = do
newContents <- updateWidget (contentsController wbc) update
return wbc {contentsController = newContents}