{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Util.RemoteWindows
-- Description :  A module to find out whether the window is remote or local.
-- Copyright   :  (c) Anton Vorontsov <anton@enomsg.org> 2014
-- License     :  BSD-style (as xmonad)
--
-- Maintainer  :  Anton Vorontsov <anton@enomsg.org>
-- Stability   :  unstable
-- Portability :  unportable
--
-- This module implements a proper way of finding out whether the window
-- is remote or local.
--
-- Just checking for a hostname and WM_CLIENT_MACHINE being equal is often
-- not enough because the hostname is a changing subject (without any
-- established notification mechanisms), and thus WM_CLIENT_MACHINE and
-- the hostname can diverge even for a local window.
--
-- This module solves the problem. As soon as there is a new window
-- created, we check the hostname and WM_CLIENT_MACHINE, and then we cache
-- the result into the XMONAD_REMOTE property.
--
-- Notice that XMonad itself does not know anything about hostnames, nor
-- does it have any dependency on Network.* modules. For this module it is
-- not a problem: you can provide a mean to get the hostname through your
-- config file (see usage). Or, if you don't like the hassle of handling
-- dynamic hostnames (suppose your hostname never changes), it is also
-- fine: this module will fallback to using environment variables.
--
-----------------------------------------------------------------------------

module XMonad.Util.RemoteWindows
    ( -- $usage
      isLocalWindow
    , manageRemote
    , manageRemoteG
    ) where

import XMonad
import XMonad.Util.WindowProperties
import XMonad.Prelude
import System.Posix.Env

-- $usage
-- You can use this module with the following in your @xmonad.hs@:
--
-- > import XMonad
-- > import XMonad.Util.RemoteWindows
-- > import Network.BSD
-- >
-- > main = xmonad def
-- >    { manageHook = manageRemote =<< io getHostName }

guessHostName :: IO String
guessHostName :: IO String
guessHostName = forall {a}. [Maybe [a]] -> [a]
pickOneMaybe forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (String -> IO (Maybe String)
getEnv forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
`mapM` [String]
vars)
  where
    pickOneMaybe :: [Maybe [a]] -> [a]
pickOneMaybe = forall a. [a] -> a
last forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (m :: * -> *) a. MonadPlus m => m a
mzeroforall a. a -> [a] -> [a]
:) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Int -> [a] -> [a]
take Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [Maybe a] -> [a]
catMaybes
    vars :: [String]
vars = [String
"XAUTHLOCALHOSTNAME",String
"HOST",String
"HOSTNAME"]

setRemoteProp :: Window -> String -> X ()
setRemoteProp :: Window -> String -> X ()
setRemoteProp Window
w String
host = do
    Display
d <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks XConf -> Display
display
    Window
p <- String -> X Window
getAtom String
"XMONAD_REMOTE"
    Bool
v <- Property -> Window -> X Bool
hasProperty (String -> Property
Machine String
host) Window
w
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ Display -> Window -> Window -> Window -> CInt -> [CLong] -> IO ()
changeProperty32 Display
d Window
w Window
p Window
cARDINAL CInt
propModeReplace
                          [forall a b. (Integral a, Num b) => a -> b
fromIntegral forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Enum a => a -> Int
fromEnum forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not Bool
v]

-- | Given a window, tell if it is a local or a remote process. Normally,
-- it checks XMONAD_REMOTE property. If it does not exist (i.e. the
-- manageRemote hook was not deployed in user's config), it falls back to
-- checking environment variables and assuming that hostname never
-- changes.
isLocalWindow :: Window -> X Bool
isLocalWindow :: Window -> X Bool
isLocalWindow Window
w = String -> Window -> X (Maybe [CLong])
getProp32s String
"XMONAD_REMOTE" Window
w forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Just [CLong
y] -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ CLong
y forall a. Eq a => a -> a -> Bool
== CLong
0
    Maybe [CLong]
_ -> forall (m :: * -> *) a. MonadIO m => IO a -> m a
io IO String
guessHostName forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \String
host -> Property -> Window -> X Bool
hasProperty (String -> Property
Machine String
host) Window
w

-- | Use this hook to let XMonad properly track remote/local windows. For
-- example, @manageHook = manageRemote =<< io getHostName@.
manageRemote :: String -> ManageHook
manageRemote :: String -> ManageHook
manageRemote String
host = forall r (m :: * -> *). MonadReader r m => m r
ask forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> forall a. X a -> Query a
liftX (Window -> String -> X ()
setRemoteProp Window
w String
host) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Monoid a => a
mempty

-- | Use this hook if you want to manage XMONAD_REMOTE properties, but
-- don't want to use an external getHostName in your config. That way you
-- are retreating to environment variables.
manageRemoteG :: ManageHook
manageRemoteG :: ManageHook
manageRemoteG = String -> ManageHook
manageRemote forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall (m :: * -> *) a. MonadIO m => IO a -> m a
io IO String
guessHostName