module Network.URI.XDG.DesktopEntry(launchApp, launchApp', desktop2app) where

import Data.Maybe (fromMaybe, catMaybes, isJust)
import Data.List (isInfixOf)
import Control.Exception (catch)
import System.Environment (lookupEnv)
import Control.Monad (forM)
import System.Directory (doesFileExist)
import System.FilePath

import Network.URI
import System.Process (spawnCommand)

import Network.URI.XDG.Ini
import Network.URI.XDG.MimeApps (split, fromMaybe')
import Network.URI.Types (Application(..))

launchApp' :: [String] -- ^ The locale to use
             -> URI -- ^ The URI to have it open.
             -> String -- ^ The .desktop ID
             -> IO (Either String Bool) -- ^ The localized name of the application or whether it expects a local path.
launchApp' :: [String] -> URI -> String -> IO (Either String Bool)
launchApp' [String]
locales URI
uri String
desktopID = do
    INI
app <- String -> IO INI
readDesktopID String
desktopID
    let grp :: String
grp = String
"desktop entry"
    let name :: String
name = forall a. a -> Maybe a -> a
fromMaybe String
desktopID forall a b. (a -> b) -> a -> b
$ [String] -> String -> String -> INI -> Maybe String
iniLookupLocalized [String]
locales String
grp String
"name" INI
app
    case (String -> String -> INI -> Maybe String
iniLookup String
grp String
"type" INI
app, String -> String -> INI -> Maybe String
iniLookup String
grp String
"exec" INI
app) of
        (Just String
"Application", Just String
exec) | URI -> String
uriScheme URI
uri forall a. Eq a => a -> a -> Bool
/= String
"file:" Bool -> Bool -> Bool
&& (forall a. Eq a => [a] -> [a] -> Bool
isInfixOf String
"%f" String
exec Bool -> Bool -> Bool
|| forall a. Eq a => [a] -> [a] -> Bool
isInfixOf String
"%F" String
exec) ->
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right Bool
True
        (Just String
"Application", Just String
exec) ->
            forall e a. Exception e => IO a -> (e -> IO a) -> IO a
catch (forall a. URI -> String -> String -> INI -> IO (Either String a)
execApp URI
uri String
exec String
name INI
app) forall a. IOError -> IO (Either a Bool)
execFailed
        (Maybe String, Maybe String)
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right Bool
False

launchApp :: [String] -- ^ The locale to use
             -> URI -- ^ The URI to have it open.
             -> String -- ^ The .desktop ID
             -> IO (Maybe String) -- ^ The localized name of the application
launchApp :: [String] -> URI -> String -> IO (Maybe String)
launchApp [String]
a URI
b String
c = forall {a} {b}. Either a b -> Maybe a
leftToMaybe forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [String] -> URI -> String -> IO (Either String Bool)
launchApp' [String]
a URI
b String
c

readDesktopID :: String -> IO INI
readDesktopID String
desktopID = do
    Maybe String
dirs <- String -> IO (Maybe String)
lookupEnv String
"XDG_DATA_DIRS"
    let dirs' :: [String]
dirs' = forall {a}. Eq a => a -> [a] -> [[a]]
split Char
':' forall a b. (a -> b) -> a -> b
$ String -> Maybe String -> String
fromMaybe' String
"/usr/local/share/:/usr/share/" Maybe String
dirs
    [Maybe String]
filepaths <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM (forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
/= String
"") [String]
dirs') forall a b. (a -> b) -> a -> b
$ \String
dir -> do
        Bool
exists <- String -> IO Bool
doesFileExist (String
dir String -> String -> String
</> String
"applications" String -> String -> String
</> String
desktopID)
        if Bool
exists then
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a
Just (String
dir String -> String -> String
</> String
"applications" String -> String -> String
</> String
desktopID)
        else
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing -- TODO? Handle cases where - = subdirectory path?
    case forall a. [Maybe a] -> [a]
catMaybes [Maybe String]
filepaths of
        (String
filepath:[String]
_) -> do
            String
source <- String -> IO String
readFile String
filepath
            let metadata :: (String, [String])
metadata = (String
" ", [String
"filename", String
filepath]) -- Used by %k macro
            forall (m :: * -> *) a. Monad m => a -> m a
return (String -> INI
parseIni String
source)
        [] -> forall (m :: * -> *) a. Monad m => a -> m a
return []

-- Capitals usually means supports multiple arguments,
-- but HURL doesn't support making use of that.
macros :: URI -> String -> (INI, a) -> String
macros uri :: URI
uri@URI {uriScheme :: URI -> String
uriScheme=String
"file:", uriPath :: URI -> String
uriPath=String
f} (Char
'%':Char
'f':String
cmd) (INI, a)
x = forall {a}. Show a => a -> String
esc String
f forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros uri :: URI
uri@URI {uriScheme :: URI -> String
uriScheme=String
"file:", uriPath :: URI -> String
uriPath=String
f} (Char
'%':Char
'F':String
cmd) (INI, a)
x = forall {a}. Show a => a -> String
esc String
f forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros URI
uri (Char
'%':Char
'u':String
cmd) (INI, a)
x = forall {a}. Show a => a -> String
esc URI
uri forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros URI
uri (Char
'%':Char
'U':String
cmd) (INI, a)
x = forall {a}. Show a => a -> String
esc URI
uri forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros URI
uri (Char
'%':Char
'i':String
cmd) (INI
app, a
name)
    | Just String
icon <- String -> String -> INI -> Maybe String
iniLookup String
"desktop entry" String
"icon" INI
app =
        String
"--icon " forall a. [a] -> [a] -> [a]
++ forall {a}. Show a => a -> String
esc String
icon forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI
app, a
name)
    | Bool
otherwise = URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI
app, a
name)
macros URI
uri (Char
'%':Char
'c':String
cmd) (INI
app, a
name) = forall {a}. Show a => a -> String
esc a
name forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI
app, a
name)
macros URI
uri (Char
'%':Char
'k':String
cmd) (INI
app, a
name)
    | Just String
file <- String -> String -> INI -> Maybe String
iniLookup String
" " String
"filename" INI
app = forall {a}. Show a => a -> String
esc String
file forall a. [a] -> [a] -> [a]
++ URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI
app, a
name)
    | Bool
otherwise = URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI
app, a
name)
macros URI
uri (Char
'%':Char
'%':String
cmd) (INI, a)
x = Char
'%' forall a. a -> [a] -> [a]
: URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros URI
uri (Char
c:String
cmd) (INI, a)
x = Char
c forall a. a -> [a] -> [a]
: URI -> String -> (INI, a) -> String
macros URI
uri String
cmd (INI, a)
x
macros URI
_ [] (INI, a)
_ = []

esc :: a -> String
esc a
txt = Char
'\'' forall a. a -> [a] -> [a]
: String -> String
esc' (forall {a}. Show a => a -> String
show a
txt)
esc' :: String -> String
esc' (Char
'\'':String
cs) = Char
'\\' forall a. a -> [a] -> [a]
: Char
'\'' forall a. a -> [a] -> [a]
: String -> String
esc' String
cs
esc' (Char
c:String
cs) = Char
c forall a. a -> [a] -> [a]
: String -> String
esc' String
cs
esc' [] = String
"'"

execApp :: URI -> String -> String -> INI -> IO (Either String a)
execApp :: forall a. URI -> String -> String -> INI -> IO (Either String a)
execApp URI
uri String
exec String
name INI
app = do
    String -> IO ProcessHandle
spawnCommand forall a b. (a -> b) -> a -> b
$ forall {a}. Show a => URI -> String -> (INI, a) -> String
macros URI
uri String
exec (INI
app, String
name)
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. a -> Either a b
Left String
name

execFailed :: IOError -> IO (Either a Bool)
execFailed :: forall a. IOError -> IO (Either a Bool)
execFailed IOError
_ = forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right Bool
False

desktop2app :: [String] -> String -> IO (Maybe Application)
desktop2app :: [String] -> String -> IO (Maybe Application)
desktop2app [String]
locales String
desktopId = do
    INI
app <- String -> IO INI
readDesktopID String
desktopId
    let grp :: String
grp = String
"desktop entry"
    let localized :: String -> Maybe String
localized String
key = [String] -> String -> String -> INI -> Maybe String
iniLookupLocalized [String]
locales String
grp String
key INI
app
    let isApp :: Bool
isApp = String -> String -> INI -> Maybe String
iniLookup String
grp String
"type" INI
app forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"Application" Bool -> Bool -> Bool
&& forall a. Maybe a -> Bool
isJust (String -> String -> INI -> Maybe String
iniLookup String
grp String
"exec" INI
app)
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ if Bool
isApp then forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Application {
        name :: String
name = forall a. a -> Maybe a -> a
fromMaybe String
desktopId forall a b. (a -> b) -> a -> b
$ String -> Maybe String
localized String
"name",
        description :: String
description = forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ String -> Maybe String
localized String
"comment",
        icon :: URI
icon = case String -> Maybe String
localized String
"icon" of
            Just String
icon -> String -> Maybe URIAuth -> String -> String -> String -> URI
URI String
"xdg-icon:" forall a. Maybe a
Nothing String
icon String
"" String
""
            Maybe String
Nothing -> String -> Maybe URIAuth -> String -> String -> String -> URI
URI String
"about:" forall a. Maybe a
Nothing String
"blank" String
"" String
"",
        appId :: String
appId = String
desktopId
    } else forall a. Maybe a
Nothing

--- Utils

leftToMaybe :: Either a b -> Maybe a
leftToMaybe (Left a
a) = forall a. a -> Maybe a
Just a
a
leftToMaybe (Right b
_) = forall a. Maybe a
Nothing