summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2011-07-12 02:56:30 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2011-07-12 02:56:30 +0200
commitade33320e5ea201a847bb9ee5522ee58b1cd8cb6 (patch)
treee688d8aa88bec3a667becd408d9e4733f3d8d1a7
downloadphi-ade33320e5ea201a847bb9ee5522ee58b1cd8cb6.tar
phi-ade33320e5ea201a847bb9ee5522ee58b1cd8cb6.zip
Implemented basic panel windows with pseudo-transparency
-rw-r--r--.gitignore2
-rw-r--r--LICENSE24
-rw-r--r--Setup.lhs3
-rw-r--r--phi.cabal21
-rw-r--r--src/Phi.hs6
-rw-r--r--src/Phi/Panel.hs40
-rw-r--r--src/Phi/X11.hs236
7 files changed, 332 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..733412c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*~
+dist
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1214105
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2011, Matthias Schiffer <mschiffer@universe-factory.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the <organization> nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
index 0000000..5bde0de
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/phi.cabal b/phi.cabal
new file mode 100644
index 0000000..e82b01e
--- /dev/null
+++ b/phi.cabal
@@ -0,0 +1,21 @@
+cabal-version: >= 1.2
+name: phi
+version: 0.1
+synopsis: An X panel
+description: An X panel
+category: X11
+license: BSD3
+license-file: LICENSE
+author: Matthias Schiffer
+maintainer: mschiffer@universe-factory.net
+build-type: Simple
+
+library
+ build-depends: base >= 4, mtl, cairo, X11
+ exposed-modules: Phi.Panel
+ hs-source-dirs: src
+
+executable Phi
+ build-depends: base >= 4
+ hs-source-dirs: src
+ main-is: Phi.hs
diff --git a/src/Phi.hs b/src/Phi.hs
new file mode 100644
index 0000000..9aa6ad0
--- /dev/null
+++ b/src/Phi.hs
@@ -0,0 +1,6 @@
+import Phi.Panel
+import Phi.X11
+
+main :: IO ()
+main = do
+ initPhi phiDefaultXConfig { phiPanelPosition = PanelPositionBottom } \ No newline at end of file
diff --git a/src/Phi/Panel.hs b/src/Phi/Panel.hs
new file mode 100644
index 0000000..23b022f
--- /dev/null
+++ b/src/Phi/Panel.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE ExistentialQuantification #-}
+
+module Phi.Panel ( Panel(..)
+ , PanelClass(..)
+ , (<~>)
+ , separator
+ ) where
+
+import Data.Function
+
+class PanelClass a where
+ minSize :: a -> Int
+
+ weight :: a -> Float
+ weight _ = 0
+
+data Panel = forall a. PanelClass a => Panel a
+
+instance PanelClass Panel where
+ minSize (Panel p) = minSize p
+ weight (Panel p) = weight p
+
+data CompoundPanel = CompoundPanel Panel Panel
+
+instance PanelClass CompoundPanel where
+ minSize (CompoundPanel a b) = ((+) `on` minSize) a b
+ weight (CompoundPanel a b) = ((+) `on` weight) a b
+
+(<~>) :: Panel -> Panel -> Panel
+a <~> b = Panel $ CompoundPanel a b
+
+
+data Separator = Separator Int Float
+
+instance PanelClass Separator where
+ minSize (Separator s _) = s
+ weight (Separator _ w) = w
+
+separator :: Int -> Float -> Panel
+separator s w = Panel $ Separator s w
diff --git a/src/Phi/X11.hs b/src/Phi/X11.hs
new file mode 100644
index 0000000..06bebfa
--- /dev/null
+++ b/src/Phi/X11.hs
@@ -0,0 +1,236 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Phi.X11 ( PanelPosition(..)
+ , PhiXConfig(..)
+ , phiDefaultXConfig
+ , initPhi
+ ) where
+
+import Graphics.X11.Xlib
+import Graphics.X11.Xlib.Extras
+import Graphics.X11.Xinerama
+
+import Control.Monad
+import Data.Maybe
+import Data.Bits
+
+import Control.Monad.State
+import Control.Monad.Trans
+
+data PanelPosition = PanelPositionTop | PanelPositionBottom
+
+data PhiXConfig = PhiXConfig { phiXScreenInfo :: Display -> IO [Rectangle]
+ , phiPanelPosition :: PanelPosition
+ , phiPanelSize :: Int
+ }
+
+data PhiState = PhiState { phiXConfig :: PhiXConfig
+ , phiDisplay :: Display
+ , phiRootPixmap :: Pixmap
+ , phiPanels :: [PanelState]
+ }
+
+data PanelState = PanelState { panelWindow :: Window
+ , panelGC :: GC
+ , panelArea :: Rectangle
+ , panelScreenArea :: Rectangle
+ }
+
+newtype Phi a = Phi (StateT PhiState IO a)
+ deriving (Monad, MonadState PhiState, MonadIO)
+
+runPhi :: PhiState -> Phi a -> IO (a, PhiState)
+runPhi st (Phi a) = runStateT a st
+
+
+phiDefaultXConfig = PhiXConfig { phiXScreenInfo = getScreenInfo
+ , phiPanelPosition = PanelPositionTop
+ , phiPanelSize = 24
+ }
+
+
+initPhi :: PhiXConfig -> IO ()
+initPhi config = do
+ disp <- openDisplay []
+ selectInput disp (defaultRootWindow disp) $ propertyChangeMask.|.structureNotifyMask
+
+ runPhi PhiState { phiXConfig = config, phiDisplay = disp, phiRootPixmap = 0, phiPanels = [] } $ do
+ updateRootPixmap
+
+ screens <- liftIO $ phiXScreenInfo config disp
+ panels <- mapM createPanel screens
+ forM_ panels $ \panel -> do
+ setPanelProperties panel
+ liftIO $ mapWindow disp (panelWindow panel)
+
+ modify $ \state -> state { phiPanels = panels }
+
+ state <- get
+ liftIO $ allocaXEvent $ \xevent -> runPhi state $ do
+ forever $ do
+ liftIO $ nextEvent disp xevent
+ event <- liftIO $ getEvent xevent
+
+ case event of
+ ExposeEvent {} -> updatePanels
+ PropertyEvent {} -> handlePropertyUpdate event
+ _ -> return ()
+ return ()
+
+
+updatePanels :: Phi ()
+updatePanels = do
+ disp <- gets phiDisplay
+ rootPixmap <- gets phiRootPixmap
+ panels <- gets phiPanels
+ forM_ panels $ \panel -> liftIO $ withRectangle (panelArea panel) (copyArea disp rootPixmap (panelWindow panel) (panelGC panel)) 0 0
+
+
+handlePropertyUpdate :: Event -> Phi ()
+handlePropertyUpdate PropertyEvent { ev_atom = atom } = do
+ disp <- gets phiDisplay
+ panels <- gets phiPanels
+ atom_XROOTPMAP_ID <- liftIO $ internAtom disp "_XROOTPMAP_ID" False
+ atom_XROOTMAP_ID <- liftIO $ internAtom disp "_XROOTMAP_ID" False
+
+ when (atom == atom_XROOTPMAP_ID || atom == atom_XROOTMAP_ID) $ do
+ updateRootPixmap
+ updatePanels
+
+
+updateRootPixmap :: Phi ()
+updateRootPixmap = do
+ disp <- gets phiDisplay
+ let screen = defaultScreen disp
+ rootwin = defaultRootWindow disp
+ atom_XROOTPMAP_ID <- liftIO $ internAtom disp "_XROOTPMAP_ID" False
+ atom_XROOTMAP_ID <- liftIO $ internAtom disp "_XROOTMAP_ID" False
+ pixmap <- liftM (fromMaybe 0 . listToMaybe . join . catMaybes) $ forM [atom_XROOTPMAP_ID, atom_XROOTMAP_ID] $ \atom -> liftIO $ rawGetWindowProperty 32 disp atom rootwin
+ modify $ \state -> state { phiRootPixmap = pixmap }
+
+
+createPanel :: Rectangle -> Phi PanelState
+createPanel screen = do
+ config <- gets phiXConfig
+ let rect = panelBounds config screen
+ disp <- gets phiDisplay
+ win <- createPanelWindow rect
+ gc <- liftIO $ createGC disp win
+ return PanelState { panelWindow = win, panelGC = gc, panelArea = rect, panelScreenArea = screen }
+
+
+createPanelWindow :: Rectangle -> Phi Window
+createPanelWindow rect = do
+ disp <- gets phiDisplay
+ let screen = defaultScreen disp
+ depth = defaultDepth disp screen
+ visual = defaultVisual disp screen
+ colormap = defaultColormap disp screen
+ rootwin = defaultRootWindow disp
+ mask = cWEventMask.|.cWColormap.|.cWBackPixel.|.cWBorderPixel
+
+ liftIO $ allocaSetWindowAttributes $ \attr -> do
+ set_colormap attr colormap
+ set_background_pixel attr 0
+ set_border_pixel attr 0
+ set_event_mask attr exposureMask
+ withRectangle rect (createWindow disp rootwin) 0 depth inputOutput visual mask attr
+
+
+setPanelProperties :: PanelState -> Phi ()
+setPanelProperties panel = do
+ disp <- gets phiDisplay
+ liftIO $ do
+ atom_NET_WM_WINDOW_TYPE <- internAtom disp "_NET_WM_WINDOW_TYPE" False
+ atom_NET_WM_WINDOW_TYPE_DOCK <- internAtom disp "_NET_WM_WINDOW_TYPE_DOCK" False
+
+ atom_NET_WM_DESKTOP <- internAtom disp "_NET_WM_DESKTOP" False
+
+ atom_NET_WM_STATE <- internAtom disp "_NET_WM_STATE" False
+ atom_NET_WM_STATE_SKIP_PAGER <- internAtom disp "_NET_WM_STATE_SKIP_PAGER" False
+ atom_NET_WM_STATE_SKIP_TASKBAR <- internAtom disp "_NET_WM_STATE_SKIP_TASKBAR" False
+ atom_NET_WM_STATE_STICKY <- internAtom disp "_NET_WM_STATE_STICKY" False
+ atom_NET_WM_STATE_BELOW <- internAtom disp "_NET_WM_STATE_BELOW" False
+
+ atom_MOTIF_WM_HINTS <- internAtom disp "_MOTIF_WM_HINTS" False
+
+ storeName disp (panelWindow panel) "Phi"
+ changeProperty32 disp (panelWindow panel) atom_NET_WM_WINDOW_TYPE aTOM propModeReplace [fromIntegral atom_NET_WM_WINDOW_TYPE_DOCK]
+ changeProperty32 disp (panelWindow panel) atom_NET_WM_DESKTOP cARDINAL propModeReplace [0xFFFFFFFF]
+ changeProperty32 disp (panelWindow panel) atom_NET_WM_STATE aTOM propModeReplace [ fromIntegral atom_NET_WM_STATE_SKIP_PAGER
+ , fromIntegral atom_NET_WM_STATE_SKIP_TASKBAR
+ , fromIntegral atom_NET_WM_STATE_STICKY
+ , fromIntegral atom_NET_WM_STATE_BELOW]
+ setWMHints disp (panelWindow panel) WMHints { wmh_flags = fromIntegral inputHintBit
+ , wmh_input = False
+ , wmh_initial_state = 0
+ , wmh_icon_pixmap = 0
+ , wmh_icon_window = 0
+ , wmh_icon_x = 0
+ , wmh_icon_y = 0
+ , wmh_icon_mask = 0
+ , wmh_window_group = 0
+ }
+ changeProperty32 disp (panelWindow panel) atom_MOTIF_WM_HINTS atom_MOTIF_WM_HINTS propModeReplace [ 2, 0, 0, 0, 0 ]
+ setStruts panel
+
+
+setStruts :: PanelState -> Phi ()
+setStruts panel = do
+ disp <- gets phiDisplay
+ config <- gets phiXConfig
+ let rootwin = defaultRootWindow disp
+ position = phiPanelPosition config
+ area = panelArea panel
+ (_, _, _, _, rootHeight, _, _) <- liftIO $ getGeometry disp rootwin
+
+ let struts = case position of
+ PanelPositionTop -> [ 0
+ , 0
+ , (fromIntegral $ rect_y area) + (fromIntegral $ rect_height area)
+ , 0
+ , 0
+ , 0
+ , 0
+ , 0
+ , (fromIntegral $ rect_x area)
+ , (fromIntegral $ rect_x area) + (fromIntegral $ rect_width area) - 1
+ , 0
+ , 0
+ ]
+ PanelPositionBottom -> [ 0
+ , 0
+ , 0
+ , (fromIntegral rootHeight) - (fromIntegral $ rect_y area)
+ , 0
+ , 0
+ , 0
+ , 0
+ , 0
+ , 0
+ , (fromIntegral $ rect_x area)
+ , (fromIntegral $ rect_x area) + (fromIntegral $ rect_width area) - 1
+ ]
+
+ liftIO $ do
+ atom_NET_WM_STRUT <- internAtom disp "_NET_WM_STRUT" False
+ atom_NET_WM_STRUT_PARTIAL <- internAtom disp "_NET_WM_STRUT_PARTIAL" False
+
+ changeProperty32 disp (panelWindow panel) atom_NET_WM_STRUT cARDINAL propModeReplace $ take 4 struts
+ changeProperty32 disp (panelWindow panel) atom_NET_WM_STRUT_PARTIAL cARDINAL propModeReplace struts
+
+
+panelBounds :: PhiXConfig -> Rectangle -> Rectangle
+panelBounds config screenBounds = case phiPanelPosition config of
+ PanelPositionTop -> screenBounds { rect_height = fromIntegral $ phiPanelSize config }
+ PanelPositionBottom -> screenBounds { rect_height = fromIntegral $ phiPanelSize config,
+ rect_y = (rect_y screenBounds) + (fromIntegral $ rect_height screenBounds) - (fromIntegral $ phiPanelSize config) }
+
+withRectangle :: Rectangle -> (Position -> Position -> Dimension -> Dimension -> a) -> a
+withRectangle r = withDimension r . withPosition r
+
+withPosition :: Rectangle -> (Position -> Position -> a) -> a
+withPosition r f = f (rect_x r) (rect_y r)
+
+withDimension :: Rectangle -> (Dimension -> Dimension -> a) -> a
+withDimension r f = f (rect_width r) (rect_height r)