summaryrefslogtreecommitdiffhomepage
path: root/src/lib/Xmobar/XUtil.hs
blob: c4b45317af948bd535968533b6138ec1d62c37ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{-# LANGUAGE CPP #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XUtil
-- Copyright   :  (C) 2011, 2012, 2013, 2014, 2015, 2017, 2018 Jose Antonio Ortega Ruiz
--                (C) 2007 Andrea Rossato
-- License     :  BSD3
--
-- Maintainer  :  jao@gnu.org
-- Stability   :  unstable
-- Portability :  unportable
--
-----------------------------------------------------------------------------

module Xmobar.XUtil
    ( XFont
    , initFont
    , initCoreFont
    , initUtf8Font
    , textExtents
    , textWidth
    , printString
    , nextEvent'
    ) where

import Control.Concurrent
import Control.Monad (when)
import Control.Monad.Trans
import Control.Exception (SomeException, handle)
import Data.List
import Foreign
import Graphics.X11.Xlib hiding (textExtents, textWidth)
import qualified Graphics.X11.Xlib as Xlib (textExtents, textWidth)
import Graphics.X11.Xlib.Extras
import System.Mem.Weak ( addFinalizer )
import System.Posix.Types (Fd(..))

#if defined XFT
import Xmobar.MinXft
import Graphics.X11.Xrender
#else
import System.IO(hPutStrLn, stderr)
#endif

import Xmobar.ColorCache

-- Hide the Core Font/Xft switching here
data XFont = Core FontStruct
           | Utf8 FontSet
#ifdef XFT
           | Xft  [AXftFont]
#endif

-- | When initFont gets a font name that starts with 'xft:' it switchs
-- to the Xft backend Example: 'xft:Sans-10'
initFont :: Display -> String -> IO XFont
initFont d s =
       let xftPrefix = "xft:" in
       if  xftPrefix `isPrefixOf` s then
#ifdef XFT
           fmap Xft $ initXftFont d s
#else
           do
               hPutStrLn stderr $ "Warning: Xmobar must be built with "
                   ++ "the with_xft flag to support font '" ++ s
                   ++ ".' Falling back on default."
               initFont d miscFixedFont
#endif
       else
           fmap Utf8 $ initUtf8Font d s

miscFixedFont :: String
miscFixedFont = "-misc-fixed-*-*-*-*-*-*-*-*-*-*-*-*"

-- | Given a fontname returns the font structure. If the font name is
--  not valid the default font will be loaded and returned.
initCoreFont :: Display -> String -> IO FontStruct
initCoreFont d s = do
  f <- handle fallBack getIt
  addFinalizer f (freeFont d f)
  return f
      where getIt = loadQueryFont d s
            fallBack :: SomeException -> IO FontStruct
            fallBack = const $ loadQueryFont d miscFixedFont

-- | Given a fontname returns the font structure. If the font name is
--  not valid the default font will be loaded and returned.
initUtf8Font :: Display -> String -> IO FontSet
initUtf8Font d s = do
  (_,_,f) <- handle fallBack getIt
  addFinalizer f (freeFontSet d f)
  return f
      where getIt = createFontSet d s
            fallBack :: SomeException -> IO ([String], String, FontSet)
            fallBack = const $ createFontSet d miscFixedFont

#ifdef XFT
initXftFont :: Display -> String -> IO [AXftFont]
initXftFont d s = do
  let fontNames = wordsBy (== ',') (drop 4 s)
  mapM openFont fontNames
  where
    openFont fontName = do
        f <- openAXftFont d (defaultScreenOfDisplay d) fontName
        addFinalizer f (closeAXftFont d f)
        return f
    wordsBy p str = case dropWhile p str of
                        ""   -> []
                        str' -> w : wordsBy p str''
                                where
                                    (w, str'') = break p str'
#endif

textWidth :: Display -> XFont -> String -> IO Int
textWidth _   (Utf8 fs) s = return $ fromIntegral $ wcTextEscapement fs s
textWidth _   (Core fs) s = return $ fromIntegral $ Xlib.textWidth fs s
#ifdef XFT
textWidth dpy (Xft xftdraw) s = do
    gi <- xftTxtExtents' dpy xftdraw s
    return $ xglyphinfo_xOff gi
#endif

textExtents :: XFont -> String -> IO (Int32,Int32)
textExtents (Core fs) s = do
  let (_,a,d,_) = Xlib.textExtents fs s
  return (a,d)
textExtents (Utf8 fs) s = do
  let (_,rl)  = wcTextExtents fs s
      ascent  = fromIntegral $ - (rect_y rl)
      descent = fromIntegral $ rect_height rl + fromIntegral (rect_y rl)
  return (ascent, descent)
#ifdef XFT
textExtents (Xft xftfonts) _ = do
  ascent  <- fromIntegral `fmap` xft_ascent'  xftfonts
  descent <- fromIntegral `fmap` xft_descent' xftfonts
  return (ascent, descent)
#endif

printString :: Display -> Drawable -> XFont -> GC -> String -> String
            -> Position -> Position -> String -> Int -> IO ()
printString d p (Core fs) gc fc bc x y s a = do
    setFont d gc $ fontFromFontStruct fs
    withColors d [fc, bc] $ \[fc', bc'] -> do
      setForeground d gc fc'
      when (a == 255) (setBackground d gc bc')
      drawImageString d p gc x y s

printString d p (Utf8 fs) gc fc bc x y s a =
    withColors d [fc, bc] $ \[fc', bc'] -> do
      setForeground d gc fc'
      when (a == 255) (setBackground d gc bc')
      liftIO $ wcDrawImageString d p fs gc x y s

#ifdef XFT
printString dpy drw fs@(Xft fonts) _ fc bc x y s al =
  withDrawingColors dpy drw fc bc $ \draw fc' bc' -> do
    when (al == 255) $ do
      (a,d)  <- textExtents fs s
      gi <- xftTxtExtents' dpy fonts s
      drawXftRect draw bc' x (y - a) (1 + xglyphinfo_xOff gi) (a + d + 2)
    drawXftString' draw fc' fonts (toInteger x) (toInteger y) s
#endif

-- | A version of nextEvent that does not block in foreign calls.
nextEvent' :: Display -> XEventPtr -> IO ()
nextEvent' d p = do
    pend <- pending d
    if pend /= 0
        then nextEvent d p
        else do
            threadWaitRead (Fd fd)
            nextEvent' d p
 where
    fd = connectionNumber d