1

Having the following Haskell code:

import           Control.Concurrent
import           Data.Time.Clock
import           Debug.Trace
import           Graphics.X11
import           Graphics.X11.Xlib.Extras

main :: IO ()
main = do
  d <- openDisplay ""
  loop d

loop :: Display -> IO ()
loop d = do
  time <- getCurrentTime
  (w, _) <- getInputFocus d
  maybeName <- fetchName d w
  windowAttrs <- getWindowAttributes d w
  print $ show time ++ " Name: " ++ show maybeName ++ " Width: " ++ show (wa_width windowAttrs)
  threadDelay 1000000
  loop d

The window title returned by fetchName is always Nothing.

Haskell X11 library is a wrapper around Xlib

Possibly related issues:

Răzvan Flavius Panda
  • 21,730
  • 17
  • 111
  • 169
  • 1
    I'd guess that `w` is the input focus window, which is typically a sub-sub-sub...window of the one you want, e.g. it is a single text entry widget. Perhaps you could try to access the parent, then the grandparent, and so on until you find a window having a title. – chi Apr 17 '18 at 22:18
  • @chi You are most probably right about that. The irony is that all the programs I was testing against did not have a title. – Răzvan Flavius Panda Apr 17 '18 at 22:29

2 Answers2

5

It looks like fetchName isn't always filled in. Instead you need to use the _NET_WM_NAME property:

import           Control.Concurrent
import           Data.Time.Clock
import           Debug.Trace
import           Graphics.X11
import           Graphics.X11.Xlib.Extras

main :: IO ()
main = do
  d <- openDisplay ""
  loop d

loop :: Display -> IO ()
loop d = do
  time <- getCurrentTime
  (w, _) <- getInputFocus d
  a <- internAtom d "_NET_WM_NAME" False
  p <- getTextProperty d w a
  ps <- wcTextPropertyToTextList d p
  windowAttrs <- getWindowAttributes d w
  print $ show time ++ " Name: " ++ show ps ++ " Width: " ++ show (wa_width windowAttrs)
  threadDelay 1000000
  loop d

This is what XMonad does:

https://github.com/xmonad/xmonad/blob/8b055621e92e7ade127043e968f50713c15a00a0/src/XMonad/ManageHook.hs#L71-L80

Brian McKenna
  • 45,528
  • 6
  • 61
  • 60
1

In the end I had to adapt Brian's answer to the more complete definition from XMonad (because of exceptions being thrown by some windows):

import           Control.Exception.Extensible          (SomeException (..),
                                                        bracket)
import qualified Control.Exception.Extensible          as E
import           Graphics.X11
import           Graphics.X11.Xlib.Extras

getWindowTitle :: Display -> IO String
getWindowTitle d = do
  (w, _) <- getInputFocus d
  let getProp =
          (internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
              `E.catch` \(SomeException _) -> getTextProperty d w wM_NAME
      extract prop = do l <- wcTextPropertyToTextList d prop
                        return $ if null l then "" else head l
  bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return ""
Răzvan Flavius Panda
  • 21,730
  • 17
  • 111
  • 169