Home > Software design >  xmonad-contrib Prompt: Execute terminal prompt in a particular workspace?
xmonad-contrib Prompt: Execute terminal prompt in a particular workspace?

Time:03-06

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.)

  • Related