I recently decided to build XMonad from source via Stack to make a custom configuration. Let me preface by saying I do not have a ton of experience with Haskell. My OS is Linux - Arch.
Setup
I am attempting to make use of the XMonad.Prompt
package from xmonad-contrib in order to launch some applications.
(https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/XMonad-Prompt.html)
Currently, one prompt I am using is XMonad.Prompt.Man
, which launches the man
program with a provided argument in the default terminal (the terminal I default to is Alacritty).
(docs: https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/XMonad-Prompt-Man.html)
(src: https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/src/XMonad-Prompt-Man.html)
What I do not understand is how to use this prompt to launch the terminal to workspace 2, where I would like all my manpages to open.
The way the prompt works as of right now:
I have a keybinding set up in xmonad.hs
, something like the following:
...
, ("M-C-m", manPrompt myPromptConfig)
...
This works how the documentation says it should, but the manpages always open in the current workspace, not in workspace 2 where I want them to.
What I have tried
Because man
opens in an existing terminal and does not provide options for setting the window name, I was initially not able to use ManageHook
to detect the window name and shift it over to the correct workspace, like I do for applications like Mozilla Firefox: (https://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-ManageHook.html)
myManageHook = composeAll
[
...
, title =? "Mozilla Firefox" -> (doShift !! myWorkspaces 1)
...
]
I have tried setting the window name to something hard-coded using Alacritty's --title
option, but that only shifts the terminal after it has been spawned and man
has already been invoked on the current layout, resulting in incorrect text alignment when the terminal gets shifted to a different workspace with a different layout.
I thought a proper solution might require modifying the source code of XMonad.Prompt.Man
.
Looking at the source code for XMonad.Prompt.Man
, I saw that this line might be useful to change (line 60-63): (https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/src/XMonad-Prompt-Man.html)
manPrompt :: XPConfig -> X()
manPrompt c = do -- NOTE FROM ME: getMans and manCompl are functions defined elsewhere in the file that are not relevant here
mans <- io getMans
mkXPrompt Man c (manCompl c mans) $ runInTerm "" . ( ) "man "
Looking at the source code for runInTerm
(from XMonad.Util.Run
), it looks to me like it takes 2 string args: the first being a list of options, ""
in the case of manPrompt
(i.e. blank by default), and the second being the command to run, "man "
in the case of manPrompt
. These both get run in the default terminal (Alacritty for me).
(docs: https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Util-Run.html)
(src: https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/src/XMonad.Util.Run.html#runInTerm)
Is there something I can do to modify manPrompt
or runInTerm
so that I can specify which workspace the terminal spawned by manPrompt
spawns in? Or is there any other way anyone knows of achieving my desired behavior that I do not know about?
EDIT #1
At the suggestion of a recent answer, I am attempting to patch the code for manPrompt
to run spawnOn
from XMonad.Actions.SpawnOn
, rather than runInTerm
.
(https://hackage.haskell.org/package/xmonad-contrib-0.13/docs/XMonad-Actions-SpawnOn.html)
Here is what I have right now:
manPrompt :: XPConfig -> WorkspaceId -> String -> X ()
manPrompt promptConfig workspaceId terminalArgs = do
mans <- io getMans
mkXPrompt (showXPrompt "man ") promptConfig (manCompl promptConfig mans) $ spawnOn (workspaceId) $ (myTerminal) " " (terminalArgs) " -e man "
However, this will not compile, resulting in the error:
xmonad.hs:248:78: error:
* Couldn't match expected type `String -> X ()'
with actual type `X ()'
* In the second argument of `($)', namely
`spawnOn (workspaceId)
$ (myTerminal) " " (terminalArgs) " -e man "'
In a stmt of a 'do' block:
mkXPrompt
(showXPrompt "man ") promptConfig (manCompl promptConfig mans)
$ spawnOn (workspaceId)
$ (myTerminal) " " (terminalArgs) " -e man "
In the expression:
do mans <- io getMans
mkXPrompt
(showXPrompt "man ") promptConfig (manCompl promptConfig mans)
$ spawnOn (workspaceId)
$ (myTerminal) " " (terminalArgs) " -e man "
|
248 | mkXPrompt (showXPrompt "man ") promptConfig (manCompl promptConfig mans) $ spawnOn (workspaceId) $ (myTerminal) " " (terminalArgs) " -e man "
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I assume this issue is with my failure to grasp Haskell datatypes properly, but I do not understand...if the proper way to invoke spawnOn
is spawnOn <workspaceId> <command>
, how can I pass a String
bash command to the function as <command>
, like above?
EDIT #2 -- SOLUTION #1
My current solution is to use a custom patch of XMonad.Prompt.Man
, with a manPrompt
that looks like the following:
manPrompt :: XPConfig -> WorkspaceId -> String -> X ()
manPrompt promptConfig workspaceId terminalArgs = do
mans <- io getMans
mkXPrompt Man promptConfig (manCompl promptConfig mans) $
\input -> spawnOn workspaceId $ myTerminal " " terminalArgs " -e man " input
This compiles and works, I guess, but it seems that the use of spawnOn
still results in the same text alignment issues as with doShift
when moving the terminal windows between workspaces with different layouts. (I mentioned these in the original post under What I have tried.)
To better explain what I mean by "text alignment issues," I included a screenshot here: https://ibb.co/vxjcKZh
(This screenshot is the result of summoning the prompt on a workspace with a squished layout. I then switched my focus over to the workspace that the manpages got spawned on, which should be full-screen, but you can see, the text does not fill the entire terminal window; this is because the man
command ran when the window size was squished.)
Is there a way I can refresh the text in terminal window somehow after it spawns?
CodePudding user response:
A tweaked version of runInTerm
would indeed be a good fit here. runInTerm
is defined as:
unsafeRunInTerm, runInTerm :: String -> String -> X ()
unsafeRunInTerm options command = asks (terminal . config) >>= \t ->
unsafeSpawn $ t " " options " -e " command
runInTerm = unsafeRunInTerm
unsafeSpawn
, in turn, is an alias for spawn
from XMonad.Core
. There is a spawnOn
variation on it in XMonad.Actions.SpawnOn
which takes a WorkspaceId
in addition to the command to be ran. It seems like using spawnOn
to define a custom runInTerm
would be enough to get what you want.
On the attempt in your edit, the final argument to mkXPrompt
should be a String -> X ()
function, rather than a X ()
action. The String
argument stands for the completion chosen by the user in the prompt, which, in your case, would be the argument to man
. In the original implementation of manPrompt
, the String -> X ()
function is:
runInTerm "" . ( ) "man "
Or, rephrasing it in an arguably clearer style:
\completion -> runInTerm "" ("man " completion)
Your manPrompt
can be adjusted accordingly:
manPromptOn :: XPConfig -> WorkspaceId -> String -> X ()
manPromptOn promptConfig workspaceId terminalArgs = do
mans <- io getMans
mkXPrompt Man promptConfig (manCompl promptConfig mans) $
\completion -> spawnOn workspaceId $
myTerminal " " terminalArgs " -e man " completion
(Note that I have additionally changed the first argument to mkXPrompt
, as passing a string won't work. I'm also assuming this function is being added to something like a modified copy of the XMonad.Prompt.Man
module, as neither the Man
constructor nor the getMans
function are exported by the original version of the module.)