baskerville / bspwm

A tiling window manager based on binary space partitioning
BSD 2-Clause "Simplified" License
7.71k stars 416 forks source link

How do I "replace focused window with new window"? #1095

Open jaimet opened 4 years ago

jaimet commented 4 years ago

I have several (~10) tiled windows on my desktop, and I no longer need one of these windows. I need start an application which will create a new window. I would like the new window to replace the window that I no longer need.

I have tried to do this "manually" by:

My problem is that after closing the no-longer-needed window, focus passes back to the most-recently-focused window (which I presume is by design) so that my new window appears elsewhere (not where my now-closed-window was).

Is there some clever way of achieving this?

TIA

neeasade commented 4 years ago

switch your order of operations - wait for the new application to spawn so that the focused node gets split, and then kill it after it is split

karb94 commented 4 years ago

I had to solve a similar problem to implement "window swallowing" and there is no good solution AFAIK. @neeasade solution works but you can see how the node gets split before you kill the original which is annoying and ugly (at least for me). A slightly better solution is to substitute the original window with a receptacle hiding the window at the same time. This way you leave a receptacle behind that you can populate with the next application you open. As an example, this script replaces the current window with whatever application you are launching and places the window back after the application is closed:

#!/bin/bash
NODE_CURRENT=$(bspc query -N -n focused)
bspc node $NODE_CURRENT -i --flag hidden=on
bspc rule --add "*" --one-shot focused=on state=locked \
   node=$(bspc query -N -d -n '.leaf.!window')
$@ &
WATCH=$(bspc subscribe -c 1 node_add)
NODE_NEW=${WATCH##* }
while read EVENT
do
    [ "${EVENT##* }" = "$NODE_NEW" ] && break
done < <(bspc subscribe node_remove)
bspc node $NODE_CURRENT --flag hidden=off --focus

Problems:

All of this could be solved if there was a bspc node <command> to replace a node with a hidden window directly, a rule to replace a node or a way to have no focused window.

The lack of a better solution to replace an existing window with a new one or a hidden one is worrying since this sounds like basic window manager operations to me. If there is a better solution please let me know.

rnhmjoj commented 3 years ago

I think the real solution is to set the parent window of the program you want to open. Some programs support this, for example mpv has a --wid flag that can be used like this:

mpv --wid=$(bspc query -N -n focused)

Since most programs don't, though, I wrote a daemon that uses the bspc subscribe mechanism to hide/unhide windows opened and handle preselections. It can be put into bspwmrc and is mostly POSIX compatible.

## window swallowing options

# classes considered a terminal emulator, meaning
# to be hidden when a node is added while focused
terminal="urxvt launcher"

# classes that should never hide a `terminal`
blacklist="xev xclock"

# other combinations of parent/child where the child
# window should "swallow" the parent.
special="qutebrowser/mpv nheko/mpv"
ids=$XDG_RUNTIME_DIR/swallow

get_class() {
  xprop -id "$1" | awk -F '"' '/WM_CLASS/{print $4}'
}

save_presel() {
  test "$2" = cancel && export last_presel="$1"
}

swallow() {
  child_id=$1
  parent_id=$(bspc query -N -n last)
  this_desktop=$2
  term_desktop=$(bspc query -D -n last)
  insert_point=$3

  if test "$insert_point" = "$last_presel"; then
    unset last_presel
    return
  fi

  test "$this_desktop" != "$term_desktop" && return
  child_class=$(get_class "$child_id")
  echo "$terminal $blacklist" | grep -i "$child_class" && return

  parent_class=$(get_class "$parent_id")
  ! echo "$terminal" | grep -i "$parent_class" && \
  ! echo "$special" | grep -i "$parent_class/$child_class" && return

  echo "$child_id $parent_id" >> "$ids"
  bspc node "$parent_id" --flag hidden=on
}

spit() {
  child_id=$1
  this_desktop=$2

  grep "^$child_id" "$ids" || return

  parent_id=$(awk "/^$child_id/"'{print $2}' "$ids")
  term_desktop=$(bspc query -D -n "$parent_id")

  bspc node "$parent_id" --flag hidden=off
  test "$term_desktop" != "$this_desktop"  && bspc node "$parent_id" -d "$this_desktop"
  bspc node "$parent_id" -f
  sed -i "/^$child_id/d" "$ids"
}

# bspwm event handler
pgrep bspc && exit
bspc subscribe node_presel node_add node_remove node_flag |
while read -r msg; do
  set -- $msg
  case "$1" in
    node_presel) save_presel "$4" "$5" ;;
    node_add) swallow "$5" "$3" "$4";;
    node_remove) spit "$4" "$3" ;;
  esac
done &