apelsin-1.2.2: src/ServerInfo.hs
module ServerInfo (newServerInfo) where
import Graphics.UI.Gtk
import Control.Applicative
import Control.Monad hiding (join)
import Control.Exception
import Control.Concurrent
import Data.Ord
import Data.ByteString.Char8 (unpack)
import Data.List (sortBy)
import System.Process
import System.FilePath
import Network.Tremulous.Protocol
import qualified Network.Tremulous.StrictMaybe as SM
import Network.Tremulous.Polling
import Network.Tremulous.Util
import ConcurrentUtil
import Types
import Exception2
import List2
import Monad2
import TremFormatting
import GtkUtils
import GtkExts
import Constants
import Config
import IndividualServerSettings
import SettingsDialog
import FindPlayers
import ServerBrowser
import AutoRefresh
newServerInfo :: Bundle -> MVar (PolledHook, ClanPolledHook) -> IO (VBox, PolledHook, SetCurrent)
newServerInfo Bundle{..} mupdate = do
Config {colors} <- readMVar mconfig
current <- newEmptyMVar
running <- newEmptyMVar
-- Host name
hostnamex <- labelNew (Just "Server")
set hostnamex [
labelWrap := True
, labelJustify := JustifyCenter
, labelSelectable := True
, labelUseMarkup := True
, labelAttributes := [AttrWeight 0 (-1) WeightBold, AttrScale 0 (-1) 1.2]
]
labelSetLineWrapMode hostnamex WrapPartialWords
-- Pretty CVar table
tbl <- tableNew 0 0 True
set tbl [ tableRowSpacing := spacing
, tableColumnSpacing := spacing ]
let easyAttach pos lbl = do
a <- labelNew (Just lbl)
b <- labelNew Nothing
let h = 0x8888
labelSetAttributes a [AttrForeground 0 (-1) (Color h h h)]
set a [ miscXalign := 1 ]
set b [ miscXalign := 0, labelSelectable := True]
tableAttachDefaults tbl a 0 1 pos (pos+1)
tableAttachDefaults tbl b 1 2 pos (pos+1)
return b
let mkTable = zipWithM easyAttach [0..]
info <- mkTable ["Address", "Game", "Map", "Timelimit / SD"
, "Slots (+private)", "Ping / Average"]
versionLabel <- labelNew Nothing
bools <- labelNew Nothing
labelSetSelectable versionLabel True
labelSetSelectable bools True
-- Players
allplayers <- vBoxNew False spacing
alienshumans <- hBoxNew True spacing
GenSimple amodel aview <- playerView colors "Aliens" True
GenSimple hmodel hview <- playerView colors "Humans" True
GenSimple smodel sview <- playerView colors "Spectators" False
-- For servers not giving the P CVar:
GenSimple umodel uview <- playerView colors "Players" True
ascroll <- scrollIt aview PolicyNever PolicyAutomatic
hscroll <- scrollIt hview PolicyNever PolicyAutomatic
uscroll <- scrollIt uview PolicyNever PolicyAutomatic
sscroll <- scrollIt sview PolicyNever PolicyAutomatic
set uscroll [ widgetNoShowAll := True ]
boxPackStart alienshumans ascroll PackGrow 0
boxPackStart alienshumans hscroll PackGrow 0
boxPackStart allplayers alienshumans PackGrow 0
boxPackStart allplayers sscroll PackNatural 0
Requisition _ natural <- widgetSizeRequest sscroll
on allplayers sizeAllocate $ \(Rectangle _ _ _ h') -> do
let h = h' - spacing
Requisition _ sReq <- widgetSizeRequest sview
Requisition _ aReq <- widgetSizeRequest aview
Requisition _ hReq <- widgetSizeRequest hview
let setS play spec = do
widgetSetSizeRequest ascroll (-1) play
widgetSetSizeRequest hscroll (-1) play
widgetSetSizeRequest sscroll (-1) spec
let pReq = max hReq aReq
if natural * 2 > h then
let half = max 0 (h`quot`2) in setS half half
else if max sReq natural + pReq + spacing <= h then
setS (-1) (max sReq natural)
else do
let ratio = fromIntegral pReq / (fromIntegral sReq + fromIntegral pReq) :: Double
pNew = max natural (round (ratio * fromIntegral h))
sNew = max natural (h - pNew)
setS pNew sNew
-- Action buttons
serverbuttons <- hButtonBoxNew
buttonBoxSetLayout serverbuttons ButtonboxSpread
let action lbl icon = do
b <- buttonNewWithMnemonic lbl
set b [ buttonImage :=> imageNewFromStock icon IconSizeButton
, widgetSensitive := False ]
boxPackStartDefaults serverbuttons b
return b
st <- action "_Settings" stockProperties
refresh <- action "_Refresh" stockRefresh
join <- action "_Join" stockConnect
-- Packing
rightpane <- vBoxNew False spacing
set rightpane [ containerBorderWidth := spacing ]
boxPackStart rightpane hostnamex PackNatural 1
boxPackStart rightpane tbl PackNatural 0
boxPackStart rightpane versionLabel PackNatural 0
boxPackStart rightpane bools PackNatural 0
boxPackStart rightpane allplayers PackGrow 0
boxPackStart rightpane uscroll PackGrow 0
boxPackStart rightpane serverbuttons PackNatural spacingHalf
let launchTremulous gs = whenM (isEmptyMVar running) $ do
putMVar running ()
config <- readMVar mconfig
ss <- readMVar msettings
set join [ widgetSensitive := False ]
pid <- maybeIO $ runTremulous config gs (getSettings (address gs) ss)
case pid of
Nothing -> do
let msg = case protocolToPath config (protocol gs) of
Just path -> "Unable to run \"" ++ path ++ "\".\nHave you set your path correctly in Preferences?"
Nothing -> "Unknown game (protocol: " ++ show (protocol gs) ++ ")"
gtkError parent msg
set join [ widgetSensitive := True ]
takeMVar running
Just a -> do
autoSignal mauto AutoPause
forkIO $ do
waitForProcess a
postGUISync $ set join [ widgetSensitive := True ]
takeMVar running
autoSignal mauto AutoResume
return ()
let updateSettings joining = do
gs@GameServer{..} <- readMVar current
ss <- readMVar msettings
let cur = getSettings address ss
if not joining || null (serverPass cur) then do
new <- newSettingsDialog parent colors protected gs cur
case new of
Nothing -> return False
Just n -> do
let ss' = putSettings address n ss
swapMVar msettings ss'
unlessM (toFile ss') $
gtkWarn parent "Unable to save server specific settings"
return True
else return False
let launchWithSettings gs
| protected gs = whenM (updateSettings True) (launchTremulous gs)
| otherwise = launchTremulous gs
on join buttonActivated $ readWithMVar current launchWithSettings
on st buttonActivated $ void $ updateSettings False
let setF boolJoin gs@GameServer{..} = do
labelSetMarkup hostnamex $ showHostname colors hostname
zipWithM_ labelSetMarkup info
[ show address
, protoToFull protocol ++ (case gamemod of
SM.Nothing -> ""
SM.Just z -> " (" ++ (unpack . original) z ++ ")")
, maybeS mapname
, maybeQ timelimit ++ " / " ++ maybeQ suddendeath
, show slots ++ " (+" ++ show privslots ++ ")"
, show gameping ++ " / " ++ meanPing players
, maybeS version
]
labelSetText versionLabel (maybeS version)
labelSetText bools $ unwords [ if unlagged then "unlagged" else ""
, if protected then "password" else "" ]
listStoreClear amodel
listStoreClear hmodel
listStoreClear smodel
listStoreClear umodel
let sortedPlayers = scoreSort players
(s', a', h', u') = partitionTeams sortedPlayers
if null u' then do
mapM_ (listStoreAppend amodel) a'
mapM_ (listStoreAppend hmodel) h'
mapM_ (listStoreAppend smodel) s'
treeViewColumnsAutosize aview
treeViewColumnsAutosize hview
treeViewColumnsAutosize sview
widgetShow allplayers
widgetHide uscroll
else do
mapM_ (listStoreAppend umodel) sortedPlayers
treeViewColumnsAutosize uview
widgetShow uscroll
widgetShow uview
widgetHide allplayers
mask_ $ do
tryTakeMVar current
putMVar current gs
whenM (isEmptyMVar running) $
set join [ widgetSensitive := True ]
set refresh [ widgetSensitive := True ]
set st [ widgetSensitive := True ]
when boolJoin (launchWithSettings gs)
let updateF PollResult{..} = readWithMVar current $ \GameServer{address} ->
whenJust (serverByAddress address polled) (setF False)
on refresh buttonActivated $ readWithMVar current $ \x -> do
set refresh [ widgetSensitive := False ]
Config {delays} <- readMVar mconfig
forkIO $ do
result <- pollOne delays (address x)
SM.whenJust result $ \new -> do
pr <- do
pr@PollResult{polled} <- takeMVar mpolled
let pr' = pr
{ polled = replace
(\old -> address old == address new)
new polled
}
putMVar mpolled pr'
return pr'
(_, fb) <- readMVar mupdate
clans <- readMVar mclans
postGUISync $ do
fb clans pr
setF False new
browserUpdateOne browserStore new
playerUpdateOne playerStore new
postGUISync $
set refresh [ widgetSensitive := True ]
return ()
return (rightpane, updateF, setF)
where
validping x = x > 0 && x < 999
scoreSort = sortBy (flip (comparing kills))
showHostname _ (TI _ "") = "<i>Invalid name</i>"
showHostname colors x = pangoPretty colors x
maybeQ = SM.maybe "?" show
maybeS = SM.maybe "" (unpack . original)
meanPing = show . intmean . filter validping . map ping
protocolToPath :: Config -> Int -> Maybe FilePath
protocolToPath c proto = case proto of
69 -> Just (tremulousPath c)
70 -> Just (tremulousGppPath c)
86 -> Just (unvanquishedPath c)
_ -> Nothing
runTremulous :: Config -> GameServer -> ServerArg-> IO (Maybe ProcessHandle)
runTremulous config GameServer{..} ServerArg{..} =
case protocolToPath config protocol of
Just com -> do
(_,_,_,p) <- createProcess (proc com args) {cwd = ldir, close_fds = True}
maybe (Just p) (const Nothing) <$> getProcessExitCode p
where
ldir = case takeDirectory com of
"" -> Nothing
x -> Just x
Nothing ->
return Nothing
where
args = concatMap (uncurry arg)
[ ("connect", show address)
, ("password", serverPass)
, ("rconPassword", serverRcon)
, ("name", serverName)
]
arg :: String -> String -> [String]
arg _ "" = []
arg a s = ['+':a, s]
playerView :: ColorArray -> String -> Bool -> IO (GenSimple ListStore Player)
playerView colors teamName showScore = do
gen@(GenSimple _ view) <- newGenSimple =<< listStoreNew []
select <- treeViewGetSelection view
treeSelectionSetMode select SelectionNone
addColumn gen teamName True [cellTextEllipsize := EllipsizeEnd] $ \rend ->
cellSetMarkup rend . pangoPrettyBS colors . name
when showScore $ do
addColumn gen "Score" False [cellXAlign := 1] $ \rend item ->
set rend [cellText := show $ kills item]
return ()
addColumn gen "Ping" False [cellXAlign := 1] $ \rend item ->
set rend [cellText := show $ ping item]
return gen