Basic systray implementation

This commit is contained in:
Matthias Schiffer 2011-07-19 11:16:50 +02:00
parent 581e1f9c63
commit 19c4bb3521
7 changed files with 225 additions and 58 deletions

View file

@ -3,6 +3,7 @@
module Phi.Bindings.Util ( setClassHint module Phi.Bindings.Util ( setClassHint
, visualIDFromVisual , visualIDFromVisual
, putClientMessage , putClientMessage
, Phi.Bindings.Util.getEvent
, createXlibSurface , createXlibSurface
) where ) where
@ -51,6 +52,35 @@ putClientMessage event window message_type messageData = do
foreign import ccall unsafe "cairo-xlib.h cairo_xlib_surface_create" foreign import ccall unsafe "cairo-xlib.h cairo_xlib_surface_create"
xlibSurfaceCreate :: Display -> Drawable -> Visual -> CInt -> CInt -> IO (Ptr Surface) xlibSurfaceCreate :: Display -> Drawable -> Visual -> CInt -> CInt -> IO (Ptr Surface)
getEvent :: Display -> XEventPtr -> IO Event
getEvent display p = do
eventType <- get_EventType p
case True of
_ | eventType == clientMessage -> do
serial <- (#peek XClientMessageEvent, serial) p
send_event <- (#peek XClientMessageEvent, send_event) p
window <- (#peek XClientMessageEvent, window) p
message_type <- (#peek XClientMessageEvent, message_type) p
format <- (#peek XClientMessageEvent, format) p
let datPtr = (#ptr XClientMessageEvent, data) p
dat <- case (format::CInt) of
8 -> do a <- peekArray 20 datPtr
return $ map fromIntegral (a::[CUChar])
16 -> do a <- peekArray 10 datPtr
return $ map fromIntegral (a::[CUShort])
32 -> do a <- peekArray 5 datPtr
return $ map fromIntegral (a::[CULong])
return $ ClientMessageEvent { ev_event_type = eventType
, ev_serial = serial
, ev_send_event = send_event
, ev_event_display = display
, ev_window = window
, ev_message_type = message_type
, ev_data = dat
}
| otherwise -> Graphics.X11.Xlib.Extras.getEvent p
createXlibSurface :: Display -> Drawable -> Visual -> CInt -> CInt -> IO Surface createXlibSurface :: Display -> Drawable -> Visual -> CInt -> CInt -> IO Surface
createXlibSurface dpy drawable visual width height = do createXlibSurface dpy drawable visual width height = do
surfacePtr <- xlibSurfaceCreate dpy drawable visual width height surfacePtr <- xlibSurfaceCreate dpy drawable visual width height

View file

@ -22,7 +22,7 @@ data Phi = Phi (TChan Message)
data Message = forall a. (Typeable a, Show a) => Message a data Message = forall a. (Typeable a, Show a) => Message a
deriving instance Show Message deriving instance Show Message
data DefaultMessage = Repaint deriving (Typeable, Show) data DefaultMessage = Repaint | ResetBackground deriving (Typeable, Show)
fromMessage :: (Typeable a, Show a) => Message -> Maybe a fromMessage :: (Typeable a, Show a) => Message -> Maybe a
fromMessage (Message m) = cast m fromMessage (Message m) = cast m

View file

@ -3,6 +3,7 @@
module Phi.Widget ( Display(..) module Phi.Widget ( Display(..)
, withDisplay , withDisplay
, getAtoms , getAtoms
, getScreenWindows
, getScreens , getScreens
, unionArea , unionArea
, Widget(..) , Widget(..)
@ -28,7 +29,7 @@ import Phi.Phi
import Phi.X11.Atoms import Phi.X11.Atoms
data Display = Display (MVar Xlib.Display) Atoms [Xlib.Rectangle] data Display = Display (MVar Xlib.Display) Atoms [(Xlib.Rectangle, Xlib.Window)]
withDisplay :: MonadIO m => Display -> (Xlib.Display -> m a) -> m a withDisplay :: MonadIO m => Display -> (Xlib.Display -> m a) -> m a
withDisplay (Display dispvar _ _) f = do withDisplay (Display dispvar _ _) f = do
@ -40,8 +41,12 @@ withDisplay (Display dispvar _ _) f = do
getAtoms :: Display -> Atoms getAtoms :: Display -> Atoms
getAtoms (Display _ atoms _) = atoms getAtoms (Display _ atoms _) = atoms
getScreenWindows :: Display -> [(Xlib.Rectangle, Xlib.Window)]
getScreenWindows (Display _ _ screenWindows) = screenWindows
getScreens :: Display -> [Xlib.Rectangle] getScreens :: Display -> [Xlib.Rectangle]
getScreens (Display _ _ screens) = screens getScreens = map fst . getScreenWindows
unionArea :: Xlib.Rectangle -> Xlib.Rectangle -> Int unionArea :: Xlib.Rectangle -> Xlib.Rectangle -> Int
unionArea a b = fromIntegral $ uw*uh unionArea a b = fromIntegral $ uw*uh

View file

@ -5,11 +5,20 @@ module Phi.Widgets.Systray ( systray
import Control.Concurrent import Control.Concurrent
import Control.Monad import Control.Monad
import Control.Monad.State
import Control.Monad.Trans
import Data.Bits
import Data.IORef
import Data.Maybe import Data.Maybe
import Data.Typeable
import qualified Data.Map as M
import Foreign.C.Types import Foreign.C.Types
import Graphics.Rendering.Cairo
import Graphics.Rendering.Cairo.Types
import Graphics.X11.Xlib hiding (Display) import Graphics.X11.Xlib hiding (Display)
import qualified Graphics.X11.Xlib as Xlib import qualified Graphics.X11.Xlib as Xlib
import Graphics.X11.Xlib.Extras import Graphics.X11.Xlib.Extras
@ -22,12 +31,25 @@ import Phi.Widget
import Phi.X11.Atoms import Phi.X11.Atoms
data SystrayIconState = SystrayIconState deriving Show instance Show Display where
show _ = "Display <?>"
data SystrayState = SystrayState Rectangle [SystrayIconState] deriving Show instance Show Phi where
show _ = "Phi <?>"
instance Show (IORef a) where
show _ = "IORef <?>"
data SystrayIconState = SystrayIconState Window Window deriving Show
data SystrayState = SystrayState Phi Rectangle Int (IORef Int) [SystrayIconState] deriving Show
data Systray = Systray deriving Show data Systray = Systray deriving Show
data SystrayMessage = AddIcon Window Window | RemoveIcon Window | RenderIcon Window Window Int Int Int Int Bool
deriving (Show, Typeable)
instance WidgetClass Systray where instance WidgetClass Systray where
type WidgetData Systray = SystrayState type WidgetData Systray = SystrayState
@ -35,18 +57,33 @@ instance WidgetClass Systray where
initWidget (Systray) phi dispvar = do initWidget (Systray) phi dispvar = do
forkIO $ systrayRunner phi dispvar forkIO $ systrayRunner phi dispvar
return $ SystrayState (head . getScreens $ dispvar) [] lastReset <- newIORef 0
return $ SystrayState phi (head . getScreens $ dispvar) 0 lastReset []
minSize _ (SystrayState systrayScreen icons) height screen = case True of minSize _ (SystrayState _ systrayScreen _ _ icons) height screen = case True of
_ | screen == systrayScreen -> (length icons)*height _ | screen == systrayScreen -> (length icons)*height
| otherwise -> 0 | otherwise -> 0
weight _ = 0 weight _ = 0
render Systray (SystrayState systrayScreen icons) w h screen = case True of render Systray (SystrayState phi systrayScreen reset lastResetRef icons) w h screen = case True of
_ | screen == systrayScreen -> do _ | screen == systrayScreen -> do
return () lastReset <- liftIO $ readIORef lastResetRef
liftIO $ writeIORef lastResetRef reset
Matrix _ _ _ _ dx dy <- getMatrix
forM_ (zip [0..] icons) $ \(i, SystrayIconState midParent window) -> do
let x = round dx + i*h
y = round dy
sendMessage phi $ RenderIcon midParent window x y h h (lastReset /= reset)
| otherwise -> return () | otherwise -> return ()
handleMessage _ priv@(SystrayState phi screen reset lastReset icons) m = case (fromMessage m) of
Just (AddIcon midParent window) -> SystrayState phi screen reset lastReset ((SystrayIconState midParent window):icons)
Just (RemoveIcon window) -> SystrayState phi screen reset lastReset $ filter (\(SystrayIconState _ stateWindow) -> stateWindow /= window) icons
_ -> case (fromMessage m) of
Just ResetBackground -> SystrayState phi screen (reset+1) lastReset icons
_ -> priv
systrayRunner :: Phi -> Display -> IO () systrayRunner :: Phi -> Display -> IO ()
@ -55,13 +92,45 @@ systrayRunner phi dispvar = do
initSuccess <- withDisplay dispvar $ flip initSystray atoms initSuccess <- withDisplay dispvar $ flip initSystray atoms
case initSuccess of case initSuccess of
Just xembedWindow -> forever $ do Just xembedWindow -> flip evalStateT M.empty $ forever $ do
m <- receiveMessage phi m <- receiveMessage phi
case (fromMessage m) of case (fromMessage m) of
Just event -> Just event ->
handleEvent event phi dispvar xembedWindow handleEvent event phi dispvar xembedWindow
_ -> _ ->
return () case (fromMessage m) of
Just (RenderIcon midParent window x y w h reset) -> do
errorWindowRef <- liftIO $ newIORef []
withDisplay dispvar $ \disp -> do
liftIO $ do
sync disp False
setErrorHandler $ \disp eventptr -> do
event <- getErrorEvent eventptr
when (ev_error_code event == fromIntegral badWindow) $ do
errorWindows <- readIORef errorWindowRef
writeIORef errorWindowRef (ev_resourceid event:errorWindows)
(_, x', y', w', h', _, _) <- liftIO $ getGeometry disp midParent
let resize = (fromIntegral x) /= x' || (fromIntegral y) /= y' || (fromIntegral w) /= w' || (fromIntegral h) /= h'
when resize $ liftIO $ do
moveResizeWindow disp midParent (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h)
resizeWindow disp window (fromIntegral w) (fromIntegral h)
liftIO $ sync disp False
when (resize || reset) $ liftIO $ do
clearArea disp midParent 0 0 (fromIntegral w) (fromIntegral h) True
liftIO $ sync disp False
clearArea disp window 0 0 (fromIntegral w) (fromIntegral h) True
liftIO $ sync disp False
liftIO $ xSetErrorHandler
errorWindows <- liftIO $ readIORef errorWindowRef
mapM_ (removeIcon phi disp) errorWindows
_ ->
return ()
Nothing -> Nothing ->
return () return ()
@ -114,34 +183,92 @@ sYSTEM_TRAY_BEGIN_MESSAGE = 1
sYSTEM_TRAY_CANCEL_MESSAGE :: CInt sYSTEM_TRAY_CANCEL_MESSAGE :: CInt
sYSTEM_TRAY_CANCEL_MESSAGE = 2 sYSTEM_TRAY_CANCEL_MESSAGE = 2
xEMBED_EMBEDDED_NOTIFY :: CInt
xEMBED_EMBEDDED_NOTIFY = 0
handleEvent :: Event -> Phi -> Display -> Window -> IO () handleEvent :: Event -> Phi -> Display -> Window -> StateT (M.Map Window Window) IO ()
handleEvent ClientMessageEvent { ev_message_type = message_type, ev_data = messageData, ev_window = window } phi dispvar xembedWindow = do handleEvent message@ClientMessageEvent { ev_message_type = message_type, ev_data = messageData, ev_window = window } phi dispvar xembedWindow = do
let atoms = getAtoms dispvar let atoms = getAtoms dispvar
screenWindows = getScreenWindows dispvar
when (window == xembedWindow && message_type == atom_NET_SYSTEM_TRAY_OPCODE atoms) $ do when (window == xembedWindow && message_type == atom_NET_SYSTEM_TRAY_OPCODE atoms) $ do
case messageData of case messageData of
(_:opcode:iconID:_) -> do _:opcode:iconID:_ -> do
case True of case True of
_ | opcode == sYSTEM_TRAY_REQUEST_DOCK -> _ | opcode == sYSTEM_TRAY_REQUEST_DOCK -> do
when (iconID /= 0) $ addIcon phi dispvar $ fromIntegral iconID when (iconID /= 0) $ withDisplay dispvar $ \disp -> addIcon phi disp (getAtoms dispvar) (snd . head $ screenWindows) $ fromIntegral iconID
| opcode == sYSTEM_TRAY_BEGIN_MESSAGE || opcode == sYSTEM_TRAY_CANCEL_MESSAGE -> | opcode == sYSTEM_TRAY_BEGIN_MESSAGE || opcode == sYSTEM_TRAY_CANCEL_MESSAGE ->
return () return ()
| otherwise -> do | otherwise -> do
putStrLn "Phi: unknown tray message" liftIO $ putStrLn "Phi: unknown tray message"
return () return ()
_ -> _ ->
return () return ()
handleEvent message@UnmapEvent { ev_window = window } phi dispvar xembedWindow =
withDisplay dispvar $ flip (removeIcon phi) window
handleEvent message@DestroyWindowEvent { ev_window = window } phi dispvar xembedWindow =
withDisplay dispvar $ flip (removeIcon phi) window
handleEvent _ _ _ _ = return () handleEvent _ _ _ _ = return ()
addIcon :: Phi -> Display -> Window -> IO () addIcon :: Phi -> Xlib.Display -> Atoms -> Window -> Window -> StateT (M.Map Window Window) IO ()
addIcon phi display window = do addIcon phi disp atoms panelWindow window = do
return () liftIO $ selectInput disp window $ structureNotifyMask .|. propertyChangeMask
errorRef <- liftIO $ newIORef False
midParent <- liftIO $ createSimpleWindow disp panelWindow (-16) (-16) 16 16 0 0 0
liftIO $ do
setWindowBackgroundPixmap disp midParent 1 -- ParentRelative
sync disp False
setErrorHandler $ \disp eventptr -> do
event <- getErrorEvent eventptr
when (ev_error_code event == fromIntegral badWindow && ev_resourceid event == window) $
writeIORef errorRef True
reparentWindow disp window midParent 0 0
sync disp False
mapRaised disp midParent
mapWindow disp window
sync disp False
allocaXEvent $ \event -> do
putClientMessage event window (atom_XEMBED atoms) [fromIntegral currentTime, fromIntegral xEMBED_EMBEDDED_NOTIFY, 0, fromIntegral midParent, 0]
sendEvent disp window False 0xFFFFFF event
sync disp False
xSetErrorHandler
error <- liftIO $ readIORef errorRef
case error of
False -> do
sendMessage phi $ AddIcon midParent window
sendMessage phi Repaint
modify $ M.insert window midParent
True ->
liftIO $ destroyWindow disp midParent
removeIcon :: Phi -> Xlib.Display -> Window -> StateT (M.Map Window Window) IO ()
removeIcon phi disp window = do
mmidParent <- gets $ M.lookup window
case mmidParent of
Just midParent -> do
sendMessage phi $ RemoveIcon window
sendMessage phi Repaint
liftIO $ do
reparentWindow disp window (defaultRootWindow disp) 0 0
destroyWindow disp midParent
_ ->
return ()
systray :: Widget systray :: Widget

View file

@ -41,7 +41,7 @@ data PhiState = PhiState { phiRootImage :: !Surface
} }
data PanelState = PanelState { panelWindow :: !Window data PanelState = PanelState { panelWindow :: !Window
, panelBuffer :: !Surface , panelPixmap :: !Pixmap
, panelArea :: !Rectangle , panelArea :: !Rectangle
, panelScreenArea :: !Rectangle , panelScreenArea :: !Rectangle
, panelWidgetStates :: ![Widget.WidgetState] , panelWidgetStates :: ![Widget.WidgetState]
@ -85,12 +85,15 @@ runPhi xconfig config widgets = do
screens <- liftIO $ phiXScreenInfo xconfig disp screens <- liftIO $ phiXScreenInfo xconfig disp
panelWindows <- mapM (createPanelWindow disp) screens
dispmvar <- liftIO $ newMVar disp dispmvar <- liftIO $ newMVar disp
let dispvar = Widget.Display dispmvar atoms screens let screenPanels = zip screens panelWindows
dispvar = Widget.Display dispmvar atoms screenPanels
widgetStates <- liftIO $ mapM (Widget.createWidgetState phi dispvar) widgets widgetStates <- liftIO $ mapM (Widget.createWidgetState phi dispvar) widgets
Widget.withDisplay dispvar $ \disp -> do Widget.withDisplay dispvar $ \disp -> do
panels <- mapM (createPanel disp widgetStates) screens panels <- mapM (\(screen, window) -> createPanel disp window widgetStates screen) screenPanels
forM_ panels $ \panel -> do forM_ panels $ \panel -> do
setPanelProperties disp panel setPanelProperties disp panel
@ -105,7 +108,7 @@ runPhi xconfig config widgets = do
unless available $ do unless available $ do
repaint <- gets phiRepaint repaint <- gets phiRepaint
when repaint $ do when repaint $ do
Widget.withDisplay dispvar $ flip updatePanels True updatePanels dispvar
modify $ \state -> state {phiRepaint = False} modify $ \state -> state {phiRepaint = False}
message <- receiveMessage phi message <- receiveMessage phi
@ -127,8 +130,6 @@ handleMessage dispvar m = do
modify $ \state -> state {phiRepaint = True} modify $ \state -> state {phiRepaint = True}
_ -> _ ->
case (fromMessage m) of case (fromMessage m) of
Just ExposeEvent {} ->
Widget.withDisplay dispvar $ flip updatePanels False
Just event@PropertyEvent {} -> Just event@PropertyEvent {} ->
Widget.withDisplay dispvar $ flip handlePropertyUpdate event Widget.withDisplay dispvar $ flip handlePropertyUpdate event
_ -> _ ->
@ -144,7 +145,7 @@ receiveEvents phi dispvar = do
if pend /= 0 then if pend /= 0 then
do do
liftIO $ nextEvent disp xevent liftIO $ nextEvent disp xevent
event <- liftIO $ getEvent xevent event <- liftIO $ Util.getEvent disp xevent
sendMessage phi event sendMessage phi event
return True return True
@ -152,18 +153,23 @@ receiveEvents phi dispvar = do
when (not handled) $ threadWaitRead connection when (not handled) $ threadWaitRead connection
updatePanels :: Display -> Bool -> PhiX () updatePanels :: Widget.Display -> PhiX ()
updatePanels disp redraw = do updatePanels dispvar = do
rootImage <- gets phiRootImage rootImage <- gets phiRootImage
panels <- gets phiPanels panels <- gets phiPanels
panels' <- forM panels $ \panel -> do panels' <- forM panels $ \panel -> do
let buffer = panelBuffer panel let pixmap = panelPixmap panel
area = panelArea panel area = panelArea panel
newPanel <- if not redraw then return panel else do let layoutedWidgets = (withDimension area $ Widget.layoutWidgets (panelWidgetStates panel) 0 0) $ panelScreenArea panel
let layoutedWidgets = (withDimension area $ Widget.layoutWidgets (panelWidgetStates panel) 0 0) $ panelScreenArea panel panel' = panel { panelWidgetStates = layoutedWidgets }
panel' = panel { panelWidgetStates = layoutedWidgets }
Widget.withDisplay dispvar $ \disp -> do
let screen = defaultScreen disp
visual = defaultVisual disp screen
buffer <- liftIO $ withDimension area $ Util.createXlibSurface disp pixmap visual
renderWith buffer $ do renderWith buffer $ do
withPatternForSurface rootImage $ \pattern -> do withPatternForSurface rootImage $ \pattern -> do
@ -174,20 +180,16 @@ updatePanels disp redraw = do
restore restore
Widget.renderWidgets layoutedWidgets $ panelScreenArea panel Widget.renderWidgets layoutedWidgets $ panelScreenArea panel
return panel' surfaceFinish buffer
-- copy buffer to window
liftIO $ do
setWindowBackgroundPixmap disp (panelWindow panel') pixmap
(withDimension area $ clearArea disp (panelWindow panel') 0 0) True
sync disp False
let screen = defaultScreen disp return panel'
visual = defaultVisual disp screen
surface <- liftIO $ withDimension area $ Util.createXlibSurface disp (panelWindow newPanel) visual
-- copy buffer to window
renderWith surface $ withPatternForSurface buffer $ \pattern -> do
setSource pattern
paint
surfaceFinish surface
return newPanel
modify $ \state -> state { phiPanels = panels' } modify $ \state -> state { phiPanels = panels' }
@ -199,6 +201,7 @@ handlePropertyUpdate disp PropertyEvent { ev_atom = atom } = do
when (atom == atom_XROOTPMAP_ID atoms || atom == atom_XROOTMAP_ID atoms) $ do when (atom == atom_XROOTPMAP_ID atoms || atom == atom_XROOTMAP_ID atoms) $ do
updateRootImage disp updateRootImage disp
sendMessage phi ResetBackground
sendMessage phi Repaint sendMessage phi Repaint
@ -232,26 +235,27 @@ updateRootImage disp = do
surfaceFinish rootSurface surfaceFinish rootSurface
createPanel :: Display -> [Widget.WidgetState] -> Rectangle -> PhiX PanelState createPanel :: Display -> Window -> [Widget.WidgetState] -> Rectangle -> PhiX PanelState
createPanel disp widgets screenRect = do createPanel disp win widgets screenRect = do
phi <- asks phiPhi
config <- asks phiPanelConfig config <- asks phiPanelConfig
let rect = panelBounds config screenRect let rect = panelBounds config screenRect
let screen = defaultScreen disp
win <- createPanelWindow disp rect depth = defaultDepth disp screen
buffer <- liftIO $ withDimension rect $ createImageSurface FormatRGB24 pixmap <- liftIO $ (withDimension rect $ createPixmap disp win) depth
return PanelState { panelWindow = win return PanelState { panelWindow = win
, panelBuffer = buffer , panelPixmap = pixmap
, panelArea = rect , panelArea = rect
, panelScreenArea = screenRect , panelScreenArea = screenRect
, panelWidgetStates = widgets , panelWidgetStates = widgets
} }
createPanelWindow :: Display -> Rectangle -> PhiX Window createPanelWindow :: Display -> Rectangle -> PhiX Window
createPanelWindow disp rect = do createPanelWindow disp screenRect = do
let screen = defaultScreen disp config <- asks phiPanelConfig
let rect = panelBounds config screenRect
screen = defaultScreen disp
depth = defaultDepth disp screen depth = defaultDepth disp screen
visual = defaultVisual disp screen visual = defaultVisual disp screen
colormap = defaultColormap disp screen colormap = defaultColormap disp screen

View file

@ -37,6 +37,7 @@ atoms = [ "UTF8_STRING"
, "_NET_CURRENT_DESKTOP" , "_NET_CURRENT_DESKTOP"
, "_NET_CLIENT_LIST" , "_NET_CLIENT_LIST"
, "_MOTIF_WM_HINTS" , "_MOTIF_WM_HINTS"
, "_XEMBED"
, "_XROOTPMAP_ID" , "_XROOTPMAP_ID"
, "_XROOTMAP_ID" , "_XROOTMAP_ID"
] ]

View file

@ -18,7 +18,7 @@ library
hs-source-dirs: lib hs-source-dirs: lib
extra-libraries: X11 extra-libraries: X11
pkgconfig-depends: cairo >= 1.2.0, cairo-xlib pkgconfig-depends: cairo >= 1.2.0, cairo-xlib
ghc-options: -fspec-constr-count=16 -threaded --ghc-options: -fspec-constr-count=16 -threaded
executable Phi executable Phi
build-depends: base >= 4, phi build-depends: base >= 4, phi