    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Util.Hacks
    -- Description :  A collection of small fixes and utilities with possibly hacky implementations.
    -- Copyright   :  (c) 2020 Leon Kowarschick
    -- License     :  BSD3-style (see LICENSE)
    --
    -- Maintainer  :  Leon Kowarschick. <thereal.elkowar@gmail.com>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- This module is a collection of random fixes, workarounds and other functions
    -- that rely on somewhat hacky implementations which may have unwanted side effects
    -- and/or are small enough to not warrant a separate module.
    --
    -- Import this module as qualified like so:
    --
    -- > import qualified XMonad.Util.Hacks as Hacks
    --
    -- and then use the functions you want as described in their respective documentation.
    --
    -----------------------------------------------------------------------------

    module XMonad.Util.Hacks (
      -- * Windowed fullscreen
      -- $windowedFullscreenFix
      windowedFullscreenFixEventHook,

      -- * Java Hack
      -- $java
      javaHack,

      -- * Stacking trays (trayer) above panels (xmobar)
      -- $raiseTrayer
      trayerAboveXmobarEventHook,
      trayAbovePanelEventHook,

      -- * Inform xmobar when trays (e.g. trayer) change width
      -- $padTrayer
      trayerPaddingXmobarEventHook,
      trayPaddingXmobarEventHook,
      trayPaddingEventHook,
      ) where

    import XMonad
    import XMonad.Hooks.StatusBar (xmonadPropLog')
    import XMonad.Prelude (All (All), fi, filterM, when)
    import System.Posix.Env (putEnv)

    -- $windowedFullscreenFix
    -- Windowed fullscreen describes the behaviour in which XMonad,
    -- by default, does not automatically put windows that request being fullscreened
    -- into actual fullscreen, but keeps them constrained
    -- to their normal window dimensions, still rendering them in fullscreen.
    --
    -- With chromium based applications like Chrome, Discord and others this
    -- can cause issues, where the window does not correctly see the size of the window
    -- when displaying the fullscreen content, thus cutting off the window content.
    --
    -- This function works around that issue by forcing the window to recalculate their
    -- dimensions after initiating fullscreen, thus making chrome-based applications
    -- behave properly when in windowed fullscreen.
    --
    -- The following gif shows the behaviour of chrome (left) without this fix
    -- compared to firefox, which already behaves as expected by default:
    -- <<https://user-images.githubusercontent.com/79924233/115355075-e61dd280-a1ec-11eb-81d3-927ca462945f.gif>>
    --
    -- Using this function, chrome will now behave as expected as well:
    -- <<https://user-images.githubusercontent.com/5300871/99186115-4dbb8780-274e-11eb-9ed2-b7815ba9e597.gif>>
    --
    -- Usage:
    -- add to handleEventHook as follows:
    --
    -- > handleEventHook = handleEventHook def <> Hacks.windowedFullscreenFixEventHook
    --

    -- | Fixes fullscreen behaviour of chromium based apps by quickly applying and undoing a resize.
    -- This causes chromium to recalculate the fullscreen window
    -- dimensions to match the actual "windowed fullscreen" dimensions.
    windowedFullscreenFixEventHook :: Event -> X All
    windowedFullscreenFixEventHook :: Event -> X All
    windowedFullscreenFixEventHook (ClientMessageEvent Dimension
    _ CULong
    _ Bool
    _ Display
    dpy Window
    win Window
    typ (CInt
    _:[CInt]
    dats)) = do
      Window
    wmstate <- String -> X Window
    getAtom String
    "_NET_WM_STATE"
      Window
    fullscreen <- String -> X Window
    getAtom String
    "_NET_WM_STATE_FULLSCREEN"
      Bool -> X () -> X ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when (Window
    typ Window -> Window -> Bool
    forall a. Eq a => a -> a -> Bool
    == Window
    wmstate Bool -> Bool -> Bool
    && Window -> CInt
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral Window
    fullscreen CInt -> [CInt] -> Bool
    forall a. Eq a => a -> [a] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `elem` [CInt]
    dats) (X () -> X ()) -> X () -> X ()
    forall a b. (a -> b) -> a -> b
    $
        Display -> Window -> (WindowAttributes -> X ()) -> X ()
    withWindowAttributes Display
    dpy Window
    win ((WindowAttributes -> X ()) -> X ())
    -> (WindowAttributes -> X ()) -> X ()
    forall a b. (a -> b) -> a -> b
    $ \WindowAttributes
    attrs ->
          IO () -> X ()
    forall a. IO a -> X a
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    liftIO (IO () -> X ()) -> IO () -> X ()
    forall a b. (a -> b) -> a -> b
    $ do
            Display -> Window -> Dimension -> Dimension -> IO ()
    resizeWindow Display
    dpy Window
    win (CInt -> Dimension
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (CInt -> Dimension) -> CInt -> Dimension
    forall a b. (a -> b) -> a -> b
    $ WindowAttributes -> CInt
    wa_width WindowAttributes
    attrs CInt -> CInt -> CInt
    forall a. Num a => a -> a -> a
    - CInt
    1) (CInt -> Dimension
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (CInt -> Dimension) -> CInt -> Dimension
    forall a b. (a -> b) -> a -> b
    $ WindowAttributes -> CInt
    wa_height WindowAttributes
    attrs)
            Display -> Window -> Dimension -> Dimension -> IO ()
    resizeWindow Display
    dpy Window
    win (CInt -> Dimension
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (CInt -> Dimension) -> CInt -> Dimension
    forall a b. (a -> b) -> a -> b
    $ WindowAttributes -> CInt
    wa_width WindowAttributes
    attrs) (CInt -> Dimension
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (CInt -> Dimension) -> CInt -> Dimension
    forall a b. (a -> b) -> a -> b
    $ WindowAttributes -> CInt
    wa_height WindowAttributes
    attrs)
      All -> X All
    forall a. a -> X a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (All -> X All) -> All -> X All
    forall a b. (a -> b) -> a -> b
    $ Bool -> All
    All Bool
    True
    windowedFullscreenFixEventHook Event
    _ = All -> X All
    forall a. a -> X a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (All -> X All) -> All -> X All
    forall a b. (a -> b) -> a -> b
    $ Bool -> All
    All Bool
    True

    -- $java
    -- Some java Applications might not work with xmonad. A common workaround would be to set the environment
    -- variable @_JAVA_AWT_WM_NONREPARENTING@ to 1. The function 'javaHack' does exactly that.
    -- Example usage:
    --
    -- > main = xmonad $ Hacks.javaHack (def {...})
    --

    -- | Fixes Java applications that don't work well with xmonad, by setting @_JAVA_AWT_WM_NONREPARENTING=1@
    javaHack :: XConfig l -> XConfig l
    javaHack :: forall (l :: * -> *). XConfig l -> XConfig l
    javaHack XConfig l
    conf = XConfig l
    conf
      { startupHook = startupHook conf
                        *> io (putEnv "_JAVA_AWT_WM_NONREPARENTING=1")
      }

    -- $raiseTrayer
    -- Placing @trayer@ on top of @xmobar@ is somewhat tricky:
    --
    -- - they both should be lowered to the bottom of the stacking order to avoid
    --   overlapping fullscreen windows
    --
    -- - the tray needs to be stacked on top of the panel regardless of which
    --   happens to start first
    --
    -- 'trayerAboveXmobarEventHook' (and the more generic
    -- 'trayAbovePanelEventHook') is an event hook that ensures the latter:
    -- whenever the tray lowers itself to the bottom of the stack, it checks
    -- whether there are any panels above it and lowers these again.
    --
    -- To ensure the former, that is having both @trayer@ and @xmobar@ lower
    -- themselves, which is a necessary prerequisite for this event hook to
    -- trigger:
    --
    -- - set @lowerOnStart = True@ and @overrideRedirect = True@ in @~/.xmobarrc@
    -- - pass @-l@ to @trayer@
    --
    -- Usage:
    --
    -- > handleEventHook = … <> Hacks.trayerAboveXmobarEventHook

    -- | Like 'trayAbovePanelEventHook', but specialised for trayer/xmobar.
    trayerAboveXmobarEventHook :: Event -> X All
    trayerAboveXmobarEventHook :: Event -> X All
    trayerAboveXmobarEventHook = Query Bool -> Query Bool -> Event -> X All
    trayAbovePanelEventHook (Query String
    className Query String -> String -> Query Bool
    forall a. Eq a => Query a -> a -> Query Bool
    =? String
    "trayer") (Query String
    appName Query String -> String -> Query Bool
    forall a. Eq a => Query a -> a -> Query Bool
    =? String
    "xmobar")

    -- | Whenever a tray window lowers itself to the bottom of the stack, look for
    -- any panels above it and lower these.
    trayAbovePanelEventHook
      :: Query Bool -- ^ tray
      -> Query Bool -- ^ panel
      -> (Event -> X All) -- ^ event hook
    trayAbovePanelEventHook :: Query Bool -> Query Bool -> Event -> X All
    trayAbovePanelEventHook Query Bool
    trayQ Query Bool
    panelQ ConfigureEvent{ev_window :: Event -> Window
    ev_window = Window
    w, ev_above :: Event -> Window
    ev_above = Window
    a} | Window
    a Window -> Window -> Bool
    forall a. Eq a => a -> a -> Bool
    == Window
    none = do
      X Bool -> X () -> X ()
    whenX (Query Bool -> Window -> X Bool
    forall a. Query a -> Window -> X a
    runQuery Query Bool
    trayQ Window
    w) (X () -> X ()) -> X () -> X ()
    forall a b. (a -> b) -> a -> b
    $ (Display -> X ()) -> X ()
    forall a. (Display -> X a) -> X a
    withDisplay ((Display -> X ()) -> X ()) -> (Display -> X ()) -> X ()
    forall a b. (a -> b) -> a -> b
    $ \Display
    dpy -> do
        Window
    rootw <- (XConf -> Window) -> X Window
    forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
    asks XConf -> Window
    theRoot
        (Window
    _, Window
    _, [Window]
    ws) <- IO (Window, Window, [Window]) -> X (Window, Window, [Window])
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    io (IO (Window, Window, [Window]) -> X (Window, Window, [Window]))
    -> IO (Window, Window, [Window]) -> X (Window, Window, [Window])
    forall a b. (a -> b) -> a -> b
    $ Display -> Window -> IO (Window, Window, [Window])
    queryTree Display
    dpy Window
    rootw
        let aboveTrayWs :: [Window]
    aboveTrayWs = (Window -> Bool) -> [Window] -> [Window]
    forall a. (a -> Bool) -> [a] -> [a]
    dropWhile (Window
    w Window -> Window -> Bool
    forall a. Eq a => a -> a -> Bool
    /=) [Window]
    ws
        [Window]
    panelWs <- (Window -> X Bool) -> [Window] -> X [Window]
    forall (m :: * -> *) a.
    Applicative m =>
    (a -> m Bool) -> [a] -> m [a]
    filterM (Query Bool -> Window -> X Bool
    forall a. Query a -> Window -> X a
    runQuery Query Bool
    panelQ) [Window]
    aboveTrayWs
        (Window -> X ()) -> [Window] -> X ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (IO () -> X ()
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    io (IO () -> X ()) -> (Window -> IO ()) -> Window -> X ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Display -> Window -> IO ()
    lowerWindow Display
    dpy) [Window]
    panelWs
      X All
    forall a. Monoid a => a
    mempty
    trayAbovePanelEventHook Query Bool
    _ Query Bool
    _ Event
    _ = X All
    forall a. Monoid a => a
    mempty

    -- $padTrayer
    -- Communicating tray (e.g., trayer) resize events to xmobar so that
    -- padding space may be reserved on xmobar for the tray.
    --
    -- Basic Usage with trayer:
    --
    -- First, add the padding hook to your @handleEventHook@ as follows:
    --
    -- > main = xmonad $ def
    -- > { ...
    -- > , handleEventHook = handleEventHook def
    -- >                  <> Hacks.trayerPaddingXmobarEventHook
    -- > }
    --
    -- Then, assuming the tray is placed on the right, update your
    -- @xmobarrc@ as follows:
    --
    -- > Config { ...
    -- >        , commands = [ ...
    -- >                     , Run XPropertyLog "_XMONAD_TRAYPAD", ... ]
    -- >        , template = " ... %_XMONAD_TRAYPAD%"
    -- >        }
    --
    -- As an example of what happens in this basic usage, consider the
    -- case where trayer updates to a width of 53 pixels.
    -- The following property will appear on the root window:
    --
    -- > _XMONAD_TRAYPAD(UTF8_STRING) = "<hspace=53/>"

    -- | A simple trayer/xmobar-specific event hook that watches for trayer window
    -- resize changes and updates the _XMONAD_TRAYPAD property with xmobar markup
    -- that leaves a gap for the trayer.
    trayerPaddingXmobarEventHook :: Event -> X All -- ^ event hook
    trayerPaddingXmobarEventHook :: Event -> X All
    trayerPaddingXmobarEventHook = Query Bool -> String -> Event -> X All
    trayPaddingXmobarEventHook (Query String
    className Query String -> String -> Query Bool
    forall a. Eq a => Query a -> a -> Query Bool
    =? String
    "trayer") String
    "_XMONAD_TRAYPAD"

    -- | A generic version of 'trayerPaddingXmobarEventHook' that
    -- allows the user to specify how to identify a tray window and the property
    -- to use with 'xmonadPropLog''. This is useful for other trays like
    -- stalonetray and also when space for more than one tray-like window needs to
    -- be reserved.
    trayPaddingXmobarEventHook
      :: Query Bool     -- ^ query to identify the tray window
      -> String         -- ^ 'xmonadPropLog'' property to use
      -> Event -> X All -- ^ resulting event hook
    trayPaddingXmobarEventHook :: Query Bool -> String -> Event -> X All
    trayPaddingXmobarEventHook Query Bool
    trayQ String
    prop = Query Bool -> (Int -> X ()) -> Event -> X All
    trayPaddingEventHook Query Bool
    trayQ Int -> X ()
    forall {a}. Show a => a -> X ()
    hspaceLog
      where hspaceLog :: a -> X ()
    hspaceLog a
    width = String -> String -> X ()
    xmonadPropLog' String
    prop (String
    "<hspace=" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ a -> String
    forall a. Show a => a -> String
    show a
    width String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    "/>")

    -- | A fully generic tray resize hook that invokes a callback whenever a
    -- tray-like window changes width.
    trayPaddingEventHook
      :: Query Bool     -- ^ query to identify the tray window
      -> (Int -> X ())  -- ^ action to perform when tray width changes
      -> Event -> X All -- ^ resulting event hook
    trayPaddingEventHook :: Query Bool -> (Int -> X ()) -> Event -> X All
    trayPaddingEventHook Query Bool
    trayQ Int -> X ()
    widthChanged ConfigureEvent{ ev_window :: Event -> Window
    ev_window = Window
    w, ev_width :: Event -> CInt
    ev_width = CInt
    wa } = do
      X Bool -> X () -> X ()
    whenX (Query Bool -> Window -> X Bool
    forall a. Query a -> Window -> X a
    runQuery Query Bool
    trayQ Window
    w) (X () -> X ()) -> X () -> X ()
    forall a b. (a -> b) -> a -> b
    $ Int -> X ()
    widthChanged (CInt -> Int
    forall a b. (Integral a, Num b) => a -> b
    fi CInt
    wa)
      X All
    forall a. Monoid a => a
    mempty
    trayPaddingEventHook Query Bool
    _ Int -> X ()
    _ Event
    _ = X All
    forall a. Monoid a => a
    mempty