packages feed

taffybar-5.2.0: src/System/Taffybar/Information/Workspaces/Model.hs

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-- |
-- Module      : System.Taffybar.Information.Workspaces.Model
-- Copyright   : (c) Ivan A. Malison
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Ivan A. Malison
-- Stability   : unstable
-- Portability : unportable
--
-- Backend-agnostic workspace/window model shared by workspace-related widgets.
module System.Taffybar.Information.Workspaces.Model
  ( WorkspaceBackend (..),
    WorkspaceSnapshot (..),
    WorkspaceEventBatch (..),
    WorkspaceEvent (..),
    WindowEvent (..),
    diffWorkspaceSnapshots,
    WorkspaceViewState (..),
    WorkspaceIdentity (..),
    WorkspaceInfo (..),
    WindowIdentity (..),
    WindowInfo (..),
  )
where

import qualified Data.Map.Strict as M
import Data.Text (Text)
import Data.Word (Word64)

data WorkspaceBackend
  = WorkspaceBackendEWMH
  | WorkspaceBackendHyprland
  deriving (Eq, Show)

-- | Snapshot of workspace state produced by a backend provider.
data WorkspaceSnapshot = WorkspaceSnapshot
  { snapshotBackend :: WorkspaceBackend,
    snapshotRevision :: Word64,
    -- | Whether window-level data was complete in this snapshot.
    snapshotWindowDataComplete :: Bool,
    snapshotWorkspaces :: [WorkspaceInfo]
  }
  deriving (Eq, Show)

-- | Delta events corresponding to a snapshot revision.
--
-- Consumers that want incremental updates can subscribe to these events,
-- while snapshot consumers keep using 'WorkspaceSnapshot'.
data WorkspaceEventBatch = WorkspaceEventBatch
  { eventBatchBackend :: WorkspaceBackend,
    eventBatchRevision :: Word64,
    eventBatchWindowDataComplete :: Bool,
    eventBatchEvents :: [WorkspaceEvent]
  }
  deriving (Eq, Show)

data WorkspaceEvent
  = WorkspaceOrderChanged [WorkspaceIdentity]
  | WorkspaceAdded WorkspaceInfo
  | WorkspaceRemoved WorkspaceIdentity
  | WorkspaceChanged WorkspaceInfo
  | WorkspaceWindowsChanged WorkspaceIdentity [WindowEvent]
  deriving (Eq, Show)

data WindowEvent
  = WindowAdded WindowInfo
  | WindowRemoved WindowIdentity
  | WindowChanged WindowInfo
  deriving (Eq, Show)

-- | Visibility/occupancy state that controls workspace presentation.
data WorkspaceViewState
  = WorkspaceActive
  | WorkspaceVisible
  | WorkspaceHidden
  | WorkspaceEmpty
  deriving (Eq, Show)

-- | Backend-neutral workspace identity.
data WorkspaceIdentity = WorkspaceIdentity
  { workspaceNumericId :: Maybe Int,
    workspaceName :: Text
  }
  deriving (Eq, Ord, Show)

-- | Backend-neutral workspace information.
data WorkspaceInfo = WorkspaceInfo
  { workspaceIdentity :: WorkspaceIdentity,
    workspaceState :: WorkspaceViewState,
    workspaceHasUrgentWindow :: Bool,
    workspaceIsSpecial :: Bool,
    workspaceWindows :: [WindowInfo]
  }
  deriving (Eq, Show)

data WindowIdentity
  = X11WindowIdentity Word64
  | HyprlandWindowIdentity Text
  deriving (Eq, Ord, Show)

-- | Backend-neutral window information.
data WindowInfo = WindowInfo
  { windowIdentity :: WindowIdentity,
    windowTitle :: Text,
    -- | Ordered class/app-id hints used for icon lookup.
    windowClassHints :: [Text],
    -- | Top-left position in global coordinates when available.
    windowPosition :: Maybe (Int, Int),
    windowUrgent :: Bool,
    windowActive :: Bool,
    windowMinimized :: Bool
  }
  deriving (Eq, Show)

-- | Produce incremental events that describe the change from an old snapshot to
-- a new one.
diffWorkspaceSnapshots :: WorkspaceSnapshot -> WorkspaceSnapshot -> [WorkspaceEvent]
diffWorkspaceSnapshots previous next =
  orderEvent ++ removedEvents ++ addedEvents ++ changedEvents
  where
    oldWorkspaces = snapshotWorkspaces previous
    newWorkspaces = snapshotWorkspaces next
    oldOrder = map workspaceIdentity oldWorkspaces
    newOrder = map workspaceIdentity newWorkspaces
    orderEvent =
      [WorkspaceOrderChanged newOrder | oldOrder /= newOrder]
    oldMap = M.fromList [(workspaceIdentity ws, ws) | ws <- oldWorkspaces]
    newMap = M.fromList [(workspaceIdentity ws, ws) | ws <- newWorkspaces]
    removedEvents =
      [WorkspaceRemoved wsId | wsId <- M.keys oldMap, M.notMember wsId newMap]
    addedEvents =
      [WorkspaceAdded ws | (wsId, ws) <- M.toList newMap, M.notMember wsId oldMap]
    changedEvents =
      concatMap mkChangedEvents sharedIds
    sharedIds = M.keys $ M.intersection oldMap newMap
    mkChangedEvents wsId =
      case (M.lookup wsId oldMap, M.lookup wsId newMap) of
        (Just oldWs, Just newWs) ->
          let oldWithoutWindows = oldWs {workspaceWindows = []}
              newWithoutWindows = newWs {workspaceWindows = []}
              metadataEvents =
                [WorkspaceChanged newWs | oldWithoutWindows /= newWithoutWindows]
              windowEvents = diffWorkspaceWindows oldWs newWs
              windowsChangedEvents =
                [ WorkspaceWindowsChanged wsId windowEvents
                | not (null windowEvents)
                ]
           in metadataEvents ++ windowsChangedEvents
        _ -> []

diffWorkspaceWindows :: WorkspaceInfo -> WorkspaceInfo -> [WindowEvent]
diffWorkspaceWindows oldWs newWs =
  removedEvents ++ addedEvents ++ changedEvents
  where
    oldWindows = workspaceWindows oldWs
    newWindows = workspaceWindows newWs
    oldMap = M.fromList [(windowIdentity w, w) | w <- oldWindows]
    newMap = M.fromList [(windowIdentity w, w) | w <- newWindows]
    removedEvents =
      [WindowRemoved wId | wId <- M.keys oldMap, M.notMember wId newMap]
    addedEvents =
      [WindowAdded w | (wId, w) <- M.toList newMap, M.notMember wId oldMap]
    changedEvents =
      [ WindowChanged newW
      | wId <- M.keys $ M.intersection oldMap newMap,
        Just oldW <- [M.lookup wId oldMap],
        Just newW <- [M.lookup wId newMap],
        oldW /= newW
      ]