- Introduction
- Installation
- Dependencies
- Usage
- License
- Imports
- Data Types
- Main
- Arguments
- Actions
- File Operations
- Error Messages
- Errors
- Help
Proid is a command-line tool that allows you to hide and show windows in X.
The easiest way to build Proid is to use Stack:
After you have installed Stack, use the following command to compile from source:
stack setup
stack installstack setup will automatically download the GHC compiler if you don’t have it. stack install will install the Proid executable into ~/.local/bin, which you should add to your PATH.
If you are on Arch Linux, you can find Proid in the AUR. Simply clone the repository and then run makepkg -si, or install it using an AUR helper like Paru.
The only dependency that is required for Proid to run is xdotool. However, you will need Stack to build Proid.
To hide the current window, you need to run proid hide. If you run it in a terminal window, it will hide the terminal. It is recommended to bind the Proid options to a keybinding so that it can work on any program.
Proid shows windows in the opposite order that you hid them in. It will show the most recently hidden one first. You can show windows using proid show.
Sometimes, you may need two lists for windows to hide. You can use proid deshide to hide a window, saving it to a special list. The next time you do proid show, that window will not show and it will instead show the window hidden normally. You can then show the special windows by doing proid desshow.
This is useful when you want to switch between seeing two windows. If you were to hide them both with proid hide, you would have to show both of them and then hide one to see it. With proid deshide, you will be able to make one of them special and one of them normal, allowing you to alternate which one you want to be shown.
This program uses GPL-3.0-or-later.
-- SPDX-FileCopyrightText: 2025 Rehpotsirhc
--
-- SPDX-License-Identifier: GPL-3.0-or-laterThis section imports some modules and sets language pragmas.
{-# LANGUAGE QuasiQuotes #-}
module Main (main) where
import Data.Char (toLower)
import Data.List (intercalate)
import Data.String.QQ (s)
import System.Console.ANSI
( Color (Red),
ColorIntensity (Vivid),
ConsoleLayer (Foreground),
SGR (Reset, SetColor),
setSGRCode,
)
import System.Directory
( doesFileExist,
getTemporaryDirectory,
renameFile,
)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import System.FilePath (takeFileName, (</>))
import System.IO (hPutStrLn, stderr)
import System.IO.Error
( catchIOError,
isDoesNotExistError,
isPermissionError,
)
import System.IO.Temp (writeSystemTempFile)
import System.Process (callProcess, readProcess)Here we define the ProidMode data type that we will use later.
data ProidMode = Read | Write deriving (Show, Read, Eq)This section gets the arguments passed into the program and passes them into handleArgs.
main :: IO ()
main = do
args <- getArgs
tmpDir <- getTemporaryDirectory
handleArgs tmpDir argsThis section handles arguments by displaying errors if the syntax isn’t met.
handleArgs :: FilePath -> [String] -> IO ()
handleArgs _ [] = do
printError "No arguments provided"
hPutStrLn stderr "Try --help for more information"
exitFailure
handleArgs _ ["--help"] = printHelp
handleArgs _ ["-h"] = printHelp
handleArgs tmpDir [action] = dispatch tmpDir (lookup action actionMap)
handleArgs _ (_ : _) = do
printError "You can only specify one argument"
hPutStrLn stderr "Try --help for more information"
exitFailureIf the syntax matches, then it will run dispatch, which will match a keyword to its corresponding function based on actionMap.
actionMap :: [(String, (FilePath -> FilePath -> IO (), String))]
actionMap =
[ ("hide", (proidHide, "proidlog")),
("show", (proidShow, "proidlog")),
("deshide", (proidHide, "desproidlog")),
("desshow", (proidShow, "desproidlog"))
]
dispatch :: t1 -> Maybe (t1 -> t2 -> IO b, t2) -> IO b
dispatch _ Nothing = do
printError "Invalid argument"
hPutStrLn stderr "Try --help for more information"
exitFailure
dispatch tmpDir (Just (action, filename)) = action tmpDir filenameActions are the core functionality of Proid. You can either hide or show windows, which are made possible with proidHide and proidShow respectively.
This function allows the user the hide windows. It uses xdotool to get the window ID and store it a log file. Then it uses xdotool to hide that window from the user. Potential errors are caught using the handlers defined above.
proidHide :: FilePath -> FilePath -> IO ()
proidHide tmpDir filename = do
proid <-
readProcess "xdotool" ["getactivewindow"] []
`catchIOError` xdotoolError
writeToFile (tmpDir </> filename) proid
callProcess "xdotool" ["windowunmap", init proid]
`catchIOError` xdotoolErrorThis function allows the user to show windows. It reads the last window ID from the log and removes that line. Then it uses xdotool to show the window again.
proidShow :: FilePath -> FilePath -> IO ()
proidShow tmpDir filename = do
proid <- eraseFromFile (tmpDir </> filename)
callProcess "xdotool" ["windowmap", proid]
`catchIOError` xdotoolErrorThis section contains the “meat” of the program; most of the functionality from previous functions are defined here. It contains the writeToFile function and the eraseFromFile function, to add and remove the window ID from the log.
This function appends text to a file. Errors are caught with handleLogError.
writeToFile :: FilePath -> String -> IO ()
writeToFile filename string =
catchIOError
( do
exists <- doesFileExist filename
if exists
then appendFile filename string
else writeFile filename string
)
( \e ->
printError (handleLogError Write filename "Couldn't access temporary directory" e)
>> exitFailure
)This function removes the last line from a file and returns that line.
eraseFromFile :: FilePath -> IO String
eraseFromFile path = do
let filename = takeFileName path
text <-
readFile path
`catchIOError` ( \e ->
printError (handleLogError Read path "No window to show" e)
>> exitFailure
)
if null text
then do
printError "No window to show"
exitFailure
else do
let list = lines text
let proid = last list
tmp <-
writeSystemTempFile filename (intercalate "\n" (init list))
`catchIOError` (\e -> printError (handleLogError Write "temporary file" "" e) >> exitFailure)
renameFile tmp path
`catchIOError` (\e -> printError (handleLogError Write path "" e) >> exitFailure)
return proidThis section defines a helper function that will print errors in red.
printError :: String -> IO ()
printError str =
hPutStrLn stderr $
setSGRCode [SetColor Foreground Vivid Red]
++ str
++ setSGRCode [Reset]This section defines some functions that will handle potential errors.
handleLogError :: ProidMode -> String -> String -> IOError -> String
handleLogError action file message e
| isDoesNotExistError e = message
| isPermissionError e =
"Couldn't "
++ map toLower (show action)
++ word
++ file
++ " due to lack of permissions"
| otherwise =
"Couldn't "
++ map toLower (show action)
++ word
++ file
where
word = if action == Read then " from " else " to "
xdotoolError :: p -> IO b
xdotoolError _ = do
printError "Failed to run xdotool (maybe it is not installed)"
exitFailureIn this section, a simple function is defined that prints a help message.
printHelp :: IO ()
printHelp = putStrLn helpMessage
where
helpMessage =
[s|
Usage: proid [OPTION]
Hides and shows windows
Options:
hide Hide the current window
show Show the most recently hidden window
deshide Hide the current window with a priority
desshow Show the most recently hidden window with a priority
--help Show this help message
Examples:
proid hide
proid show
proid deshide|]