1

I'm currently running a tiling window manager and I want to be able to use a custom function that is equivalent to one I had when I was using tmux that allowed me to run a command in all visible shells in the current window (E.G. ta cd to/dir)

The command/function was called ta meaning "to all"

I've managed to create the following function:

function ta() {
    local current_workspace="$(xdotool get_desktop)"
    local to_execute="`if [[ \"$current_workspace\" = \"\$(xdotool get_desktop)\"  ]]; then; $@; fi`"

    for pts in $(ls  /dev/pts | grep -o '[0-9]*'); do
        echo "$to_execut" > /dev/pts/$pts
    done
}

If I run the command manually like this:

te="`if [[ \"$current_workspace\" = \"\$(xdotool get_desktop)\"  ]]; then; xdotool get_desktop; fi`"

echo "$te" > /dev/pts/1

I see the output 0 in my current shell

If I try to run my ta I get nothing, and I've noticed that even for the commands that do work, are only the commands that output something, so I it's actually executing the string I'm storing and then outputting that.

Does any one have any better suggestions?

1 Answer 1

3

Why what you tried doesn't work

Terminals are two-way communication channels between the terminal provider and the application(s) running in the terminal. The terminal device represents the side of the applications: writing to it is a request for the terminal provider to display what you wrote, and reading from it is a request for the terminal provider to send the user input. So when you run echo "$to_execut" > /dev/pts/$pts, that's just some random program displaying something on the terminal.

The provider's side of the terminal is typically not exposed as a device. How that part works depends on the system and on whether it's a physical terminal or a terminal emulator. On Linux, a terminal emulator opens the device /dev/ptmx. Because this device is multiplexed, you can't just open it and obtain an equivalent handle to what the terminal emulator has. The only way to pretend to be the terminal emulator without the cooperation of the terminal emulator program is to attach a debugger to the process.

Injecting input into a terminal is a bad idea anyway. What if there's a program running in the terminal? What if there's a partially typed command at the shell prompt?

How to type into multiple windows

You can use xdotool to simulate key presses in a window of a terminal emulator. It's cumbersome and dangerous (if the shell isn't waiting at an empty prompt, this could do anything) — just like what you originally tried to do.

This only applies to shells running in a GUI window. If you use tabs in the terminal emulator, only the foreground tab will receive input. If you use a multiplexer such as screen or tmux, only the window that's currently displayed in a GUI window, if any, will receive input.

Using signals

Send a signal to the shell. This is the normal way of getting a process to react. This has limitations: there are only a few different signals, and you can't attach any information to a signal, so the recipient has to perform some predefined action. On the upside, the process gets to choose exactly when to react, and with shells in particular, they'll wait until a “safe” time (not while executing a foreground command).

To make the shell react to a signal, use the trap builtin. There are only a few choices of potential signals:

  • SIGUSR1 and SIGUSR2 have no conventional meaning. They kill the process by default, so this is potentially dangerous if you can't be sure that you're only sending the signal to processes that .
  • SIGWINCH is sent by the terminal emulator when the window size changes. It does nothing by default. Shells handle it to update the LINES and COLUMNS variable, but you can set your own trap to run in addition to that. A limitation of SIGWINCH is that the trap doesn't run at all if zsh is currently running a foreground job.

So what you can do is arrange a location, say ~/.zsh_USR1, and have instances of zsh read from that file.

USR1_COMMAND_FILE=$HOME/.zsh_USR1
trap '[[ -r $USR1_COMMAND_FILE ]] && . $USR1_COMMAND_FILE' USR1

Keep in mind that sending SIGUSR1 to all zsh processes will kill any script that hasn't set up this trap! So only send it once you've identified which zsh processes are running in a window you want to target.

4
  • Can I not just add a custom signal? But just to clarify the idea is, write what I want executed to the file, send a signal to all shells, have the shells trap the signal, the trap tells the shell to execute the file, part of the file checks if it's being executed within a shell that is in the current workspace and if it is, execute the command?
    – Thermatix
    Aug 3, 2021 at 15:45
  • No, unfortunately, it's not possible to define custom signals. Aug 3, 2021 at 15:48
  • I see, well thank you, I guess this is what I'll have to, Is there a reason there's no way at all to send a command like I want? I mean I get the security side of the argument as to why not to allow it, but to have absolutely no way, even a secure way, without this kind of... hackyness? What would you say is the best way to send the signal to the terminal? do I need to locate it by the PIDS from aux? I was trying that before by doing echo "`$to_output`" > /proc/$pid/fd/1
    – Thermatix
    Aug 3, 2021 at 15:50
  • “Send a command” isn't a generic concept. It's something each shell would have to support. I'll grant you that it is annoying that zsh doesn't have an easy way to react to things other than user input while it's sitting at a prompt. (It does have an easy way to react just before displaying a prompt or just before executing a command: precmd and preexec. These would be more robust, but if the shell is at a prompt, it wouldn't react until the next time the user runs a command.) Aug 3, 2021 at 15:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .