aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2016-07-11 18:55:26 +0300
committerEgor Tensin <Egor.Tensin@gmail.com>2016-07-11 18:55:26 +0300
commitd53c986113cef78e2b504ecd5a4bba60999a55e1 (patch)
tree445b892abcfa272153dc39968838dbdc5d34d684 /src
parentREADME update (diff)
downloadwindows-env-d53c986113cef78e2b504ecd5a4bba60999a55e1.tar.gz
windows-env-d53c986113cef78e2b504ecd5a4bba60999a55e1.zip
become a proper stack project
Diffstat (limited to 'src')
-rw-r--r--src/EnvUtils.hs81
-rw-r--r--src/RegUtils.hs91
-rw-r--r--src/Utils.hs32
3 files changed, 204 insertions, 0 deletions
diff --git a/src/EnvUtils.hs b/src/EnvUtils.hs
new file mode 100644
index 0000000..7e9bc96
--- /dev/null
+++ b/src/EnvUtils.hs
@@ -0,0 +1,81 @@
+{-
+ - Copyright 2015 Egor Tensin <Egor.Tensin@gmail.com>
+ - This file is licensed under the terms of the MIT License.
+ - See LICENSE.txt for details.
+-}
+
+module EnvUtils ( saveToRegistry
+ , saveToRegistryWithPrompt
+ , queryFromRegistry
+ , wipeFromRegistry
+ , wipeFromRegistryWithPrompt
+ , getEnv
+ , splitPaths
+ , joinPaths
+ , RegistryBasedEnvironment ( CurrentUserEnvironment, AllUsersEnvironment ) ) where
+
+import Control.Monad ( liftM, when )
+import Data.List ( intercalate )
+import Data.List.Split ( splitOn )
+import Data.Maybe ( fromMaybe )
+import qualified System.Environment ( lookupEnv )
+import System.IO.Error ( catchIOError, isDoesNotExistError )
+
+import qualified RegUtils
+import qualified Utils ( promptToContinue )
+
+data RegistryBasedEnvironment = CurrentUserEnvironment
+ | AllUsersEnvironment
+ deriving (Eq, Show)
+
+registrySubKeyPath :: RegistryBasedEnvironment -> String
+registrySubKeyPath CurrentUserEnvironment = "Environment"
+registrySubKeyPath AllUsersEnvironment = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
+
+registryKey CurrentUserEnvironment = RegUtils.hkcu
+registryKey AllUsersEnvironment = RegUtils.hklm
+
+registryKeyPath :: RegistryBasedEnvironment -> String
+registryKeyPath CurrentUserEnvironment = "HKCU\\" ++ registrySubKeyPath CurrentUserEnvironment
+registryKeyPath AllUsersEnvironment = "HKLM\\" ++ registrySubKeyPath AllUsersEnvironment
+
+saveToRegistry :: RegistryBasedEnvironment -> String -> String -> IO ()
+saveToRegistry env = RegUtils.setString (registryKey env) (registrySubKeyPath env)
+
+saveToRegistryWithPrompt :: RegistryBasedEnvironment -> String -> String -> IO ()
+saveToRegistryWithPrompt env name value = do
+ putStrLn $ "Saving variable '" ++ name ++ "' to '" ++ registryKeyPath env ++ "'..."
+ oldValue <- queryFromRegistry env name
+ putStrLn $ "\tOld value: " ++ oldValue
+ putStrLn $ "\tNew value: " ++ value
+ agreed <- Utils.promptToContinue
+ when agreed $ saveToRegistry env name value
+
+queryFromRegistry :: RegistryBasedEnvironment -> String -> IO String
+queryFromRegistry env name = catchIOError (RegUtils.getString (registryKey env) (registrySubKeyPath env) name) emptyIfDoesNotExist
+ where
+ emptyIfDoesNotExist :: IOError -> IO String
+ emptyIfDoesNotExist e = if isDoesNotExistError e then return "" else ioError e
+
+wipeFromRegistry :: RegistryBasedEnvironment -> String -> IO ()
+wipeFromRegistry env name = catchIOError (RegUtils.delValue (registryKey env) (registrySubKeyPath env) name) ignoreIfDoesNotExist
+ where
+ ignoreIfDoesNotExist :: IOError -> IO ()
+ ignoreIfDoesNotExist e = if isDoesNotExistError e then return () else ioError e
+
+wipeFromRegistryWithPrompt :: RegistryBasedEnvironment -> String -> IO ()
+wipeFromRegistryWithPrompt env name = do
+ putStrLn $ "Deleting variable '" ++ name ++ "' from '" ++ registryKeyPath env ++ "'..."
+ agreed <- Utils.promptToContinue
+ when agreed $ wipeFromRegistry env name
+
+getEnv :: String -> IO String
+getEnv = liftM (fromMaybe "") . System.Environment.lookupEnv
+
+pathSep = ";"
+
+splitPaths :: String -> [String]
+splitPaths = filter (not . null) . splitOn pathSep
+
+joinPaths :: [String] -> String
+joinPaths = intercalate pathSep . filter (not . null)
diff --git a/src/RegUtils.hs b/src/RegUtils.hs
new file mode 100644
index 0000000..eccb6ad
--- /dev/null
+++ b/src/RegUtils.hs
@@ -0,0 +1,91 @@
+{-
+ - Copyright 2015 Egor Tensin <Egor.Tensin@gmail.com>
+ - This file is licensed under the terms of the MIT License.
+ - See LICENSE.txt for details.
+-}
+
+module RegUtils ( delValue
+ , getString
+ , hkcu
+ , hklm
+ , setString ) where
+
+import Control.Exception ( bracket )
+import Data.Maybe ( fromMaybe )
+import Foreign.ForeignPtr ( withForeignPtr )
+import Foreign.Marshal.Alloc ( alloca, allocaBytes )
+import Foreign.Ptr ( castPtr, plusPtr )
+import Foreign.Storable ( peek, poke, sizeOf )
+import Graphics.Win32.Window ( sendMessage )
+import System.IO.Error ( mkIOError, doesNotExistErrorType )
+import System.Win32.Types
+import System.Win32.Registry
+
+getType :: HKEY -> String -> String -> IO (Maybe RegValueType)
+getType key subKeyPath valueName =
+ bracket (regOpenKey key subKeyPath) regCloseKey $ \key ->
+ withForeignPtr key $ \p_key ->
+ withTString valueName $ \p_valueName ->
+ alloca $ \p_type -> do
+ ret <- c_RegQueryValueEx p_key p_valueName nullPtr p_type nullPtr nullPtr
+ case ret of
+ 0x0 -> do
+ type_ <- peek p_type
+ return $ Just type_
+ 0x2 -> return Nothing
+ _ -> failWith "RegQueryValueEx" ret
+
+getString :: HKEY -> String -> String -> IO String
+getString key subKeyPath valueName =
+ bracket (regOpenKey key subKeyPath) regCloseKey $ \key ->
+ withForeignPtr key $ \p_key ->
+ withTString valueName $ \p_valueName ->
+ alloca $ \dataSizePtr -> do
+ poke dataSizePtr 0
+ ret <- c_RegQueryValueEx p_key p_valueName nullPtr nullPtr nullPtr dataSizePtr
+ case ret of
+ 0x0 -> do
+ dataSize <- peek dataSizePtr
+ let newDataSize = dataSize + fromIntegral (sizeOf (undefined :: TCHAR))
+ poke dataSizePtr newDataSize
+ allocaBytes (fromIntegral newDataSize) $ \dataPtr -> do
+ poke (castPtr $ plusPtr dataPtr $ fromIntegral dataSize) '\0'
+ failUnlessSuccess "RegQueryValueEx" $
+ c_RegQueryValueEx p_key p_valueName nullPtr nullPtr dataPtr dataSizePtr
+ peekTString $ castPtr dataPtr
+ 0x2 -> ioError $ mkIOError doesNotExistErrorType "RegQueryValueEx" Nothing $ Just (subKeyPath ++ "\\" ++ valueName)
+ _ -> failWith "RegQueryValueEx" ret
+
+setString :: HKEY -> String -> String -> String -> IO ()
+setString key subKeyPath valueName valueValue =
+ bracket (regOpenKey key subKeyPath) regCloseKey $ \subKey ->
+ withTString valueValue $ \p_valueValue -> do
+ type_ <- getType key subKeyPath valueName
+ regSetValueEx subKey valueName (fromMaybe rEG_SZ type_) p_valueValue $ (length valueValue + 1) * sizeOf (undefined :: TCHAR)
+ notifyEnvironmentUpdate
+
+notifyEnvironmentUpdate :: IO ()
+notifyEnvironmentUpdate =
+ withTString "Environment" $ \p_lparam -> do
+ let wparam = 0
+ let lparam = fromIntegral $ castPtrToUINTPtr p_lparam
+ let hwnd = castUINTPtrToPtr 0xffff
+ sendMessage hwnd wM_SETTINGCHANGE wparam lparam
+ return ()
+ where
+ wM_SETTINGCHANGE = 0x1A
+
+delValue :: HKEY -> String -> String -> IO ()
+delValue key subKeyPath valueName =
+ bracket (regOpenKey key subKeyPath) regCloseKey $ \subKey ->
+ withForeignPtr subKey $ \subKeyPtr ->
+ withTString valueName $ \p_valueName -> do
+ ret <- c_RegDeleteValue subKeyPtr p_valueName
+ notifyEnvironmentUpdate
+ case ret of
+ 0x0 -> return ()
+ 0x2 -> ioError $ mkIOError doesNotExistErrorType "RegQueryValueEx" Nothing $ Just (subKeyPath ++ "\\" ++ valueName)
+ _ -> failWith "RegDeleteValue" ret
+
+hkcu = hKEY_CURRENT_USER
+hklm = hKEY_LOCAL_MACHINE
diff --git a/src/Utils.hs b/src/Utils.hs
new file mode 100644
index 0000000..21ee67b
--- /dev/null
+++ b/src/Utils.hs
@@ -0,0 +1,32 @@
+{-
+ - Copyright 2015 Egor Tensin <Egor.Tensin@gmail.com>
+ - This file is licensed under the terms of the MIT License.
+ - See LICENSE.txt for details.
+-}
+
+module Utils where
+
+import Control.Monad ( liftM )
+import Data.Char ( toLower )
+import System.IO ( hFlush, stdout )
+
+prompt :: String -> IO String
+prompt banner = do
+ putStr banner
+ hFlush stdout
+ getLine
+
+promptYesNo :: String -> IO Bool
+promptYesNo banner = do
+ response <- liftM (map toLower) $ prompt banner
+ if response `elem` yeses
+ then return True
+ else if response `elem` noes
+ then return False
+ else promptToContinue
+ where
+ yeses = ["y", "yes"]
+ noes = ["n", "no"]
+
+promptToContinue :: IO Bool
+promptToContinue = promptYesNo "Continue? (y/n) "