AeroSpace icon indicating copy to clipboard operation
AeroSpace copied to clipboard

Feature: Implement sticky floating windows

Open nikitabobko opened this issue 2 years ago • 35 comments

https://i3wm.org/docs/userguide.html#_sticky_floating_windows

  • Implement sticky floating windows
  • Implement sticky sidebar windows (feature interaction with: flatten-workspace-tree, close-all-windows-but-current)

Note to myself: review usages of visualWorkspace

todo:

  • Make browsers PIP windows sticky by default, once this is implemented
  • Make Outlook reminder window sticky by default https://github.com/nikitabobko/AeroSpace/discussions/1251#discussioncomment-12679986

nikitabobko avatar Oct 14 '23 11:10 nikitabobko

Is it possible to have sticky non-floating windows? SImilar to this request: https://github.com/koekeishiya/yabai/issues/1843

I'd like to have a constant window on my screen as I flip through workspaces.

AlJohri avatar Jan 24 '24 17:01 AlJohri

"sticky sidebar" windows bring some challenges

  • What if users want to have several sticky sidebar windows
  • What should happen if a sticky sidebar window is moved with the mouse?
    • Should it become a regular tiling window?
    • Or should it remain sticky sidebar?
  • Should "sticky sidebar" windows be a per-monitor thing?
    • If the answer is yes, how users are supposed to move sticky sidebar window between monitors?
      • What should happen if users move a window to a monitor where another sticky sidebar windows is already presented?

But in general, I like the suggestion, I always wanted to have sticky non-floating windows in i3

nikitabobko avatar Jan 25 '24 11:01 nikitabobko

Maybe we are better off with a feature like "virtual monitors". That way you could place two virtual monitors on your physical monitor. You could cycle workspaces only on one of those virtual monitors

nikitabobko avatar Jan 25 '24 11:01 nikitabobko

Is there any update on this? I have some widget windows; it would be great if we could keep them on all workspaces.

nasyxx avatar Mar 07 '24 17:03 nasyxx

Maybe we are better off with a feature like "virtual monitors". That way you could place two virtual monitors on your physical monitor. You could cycle workspaces only on one of those virtual monitors

That's indeed a great thing to have. For i3 I typically use xrandr to split up a 5120x? physical device into three virtual monitors, the latter of which can then be used for workspace assignments.

But this does not necessarily solve the issue for floating (possibly workspace-independent) windows (assuming a certain preference for floating Zoom/Teams/etc. because it's easier to re-arrange during meetings). So I'd vote for both :)

tngafwlehmann avatar Apr 10 '24 13:04 tngafwlehmann

Has this ever been implemented? I'm looking for a way to keep FF PiP window in the same position when moving workspaces

TC-MO avatar Sep 19 '24 22:09 TC-MO

For now, I'm manually doing this when I switch workspaces:

  • Before I switch; I search to see if there are any PIP windows
  • I focus that window and move it to the target workspace (too bad aerospace doesn't let me do move-node-to-workspace --window-id <window-id> <target-ws> ... so sad 😔 )
  • Then, I switch to the target workspace.

I must say, it's a very weird and terrible experience, but at least the PIP window is "sticky"! 😐

farzadmf avatar Sep 20 '24 03:09 farzadmf

Would be super neat to keep a Zoom floating window as sticky so I can see whats going on in a meeting while I cycle through workspaces.

gservat avatar Sep 26 '24 06:09 gservat

I'm definitely in need of this feature! Hope it comes soon!

bitskc avatar Oct 11 '24 16:10 bitskc

Can we extend the functionality of exec-on-workspace-change,force specific windows to move-node-to-{{currently focused workspace}}?

PaRr0tBoY avatar Oct 13 '24 13:10 PaRr0tBoY

I thought this would be possible to jury-rig for at least one window using exec-on-workspace-change, but I'm not having much success. My attempt, in case anyone can make it work:

exec-on-workspace-change = ['/bin/bash', '-c',
  'WIN=$(aerospace list-windows --all | grep "<Window Title>" | cut -d" " -f1); [ -n "$WIN" ] && aerospace move-node-to-workspace --window-id="$WIN" "$AEROSPACE_FOCUSED_WORKSPACE"'
]

andsnpl avatar Oct 20 '24 05:10 andsnpl

@andsnpl: I think the issue with yours is the = after --window-id. This works:

exec-on-workspace-change = ['/bin/bash', '-c',
  'WIN=$(aerospace list-windows --all | grep "<Window Title>" | cut -d" " -f1); [ -n "$WIN" ] && aerospace move-node-to-workspace --window-id "$WIN" "$AEROSPACE_FOCUSED_WORKSPACE"'
]

However, it seemed to move the window to the focused workspace but it lost focus so it would be behind my other windows. I tried giving it focus as part of exec-on-workspace-change (see below), which kinda works (moves the window and gives it focus), but it's not really useable for my needs as clicking on any other window will send it to the back:

exec-on-workspace-change = ['/bin/bash', '-c',
  'WIN=$(aerospace list-windows --all | grep "<Window Title>" | cut -d" " -f1); [ -n "$WIN" ] && aerospace move-node-to-workspace --window-id "$WIN" "$AEROSPACE_FOCUSED_WORKSPACE" && aerospace focus --window-id "$WIN"'
]

gservat avatar Oct 20 '24 23:10 gservat

Edit: my bad. aerospace command was not in PATH for bash. It looks like I needed to run bash with --login to pick up the path through /etc/profile. Now I'm just curious why this wasn't an issue for you...

~~Thanks for the feedback @gservat. It still doesn't work for me. I tried echoing the winId to a file:~~

exec-on-workspace-change = ['/bin/bash', '-c',
  # 'WIN=$(aerospace list-windows --all | grep "<Window Title>" | cut -d" " -f1); [ -n "$WIN" ] && aerospace move-node-to-workspace --window-id "$WIN" "$AEROSPACE_FOCUSED_WORKSPACE"'
  'WIN=$(aerospace list-windows --all | grep "<Window Title>" | cut -d" " -f1); echo "$WIN -> $AEROSPACE_FOCUSED_WORKSPACE" > "$HOME/Desktop/floating_window.txt"'
]

~~but the value printed is empty.~~

 -> G

~~This is the case no matter what window title I'm searching for. I wonder if there is something in my config that is causing the nested aerospace invocations to not see the current window list.~~

As far as the focus/always-on-top shortcomings, I can see how that would need some built-in solution. In my case, the window I'm trying to move has that behavior anyway so it's not a problem for me.

andsnpl avatar Oct 21 '24 15:10 andsnpl

@andsnpl: aerospace lives in /opt/homebrew/bin for me. This is added to $PATH by the homebrew init script in my zsh environment. Guessing when bash is loaded by aerospace, it inherits my zsh environment? Not sure why it doesn't work for you though as I'm guessing its in your path too.

gservat avatar Oct 21 '24 23:10 gservat

@gservat ah, I think that's it. You seem to be on Apple silicon and I'm still on Intel, so homebrew has different install behavior. My aerospace lives in /usr/local/bin, not /opt/homebrew/bin.

If you're using the start-at-login config option in aerospace.toml, then AeroSpace is being started for you by launchd, as it is for me. It's not (unless I'm much mistaken) executing any zsh environment for either of us. Instead what's happening is AeroSpace is manually inserting the /opt/homebrew dir into the path as described here, and probably nobody's realized that that's not good enough for Intel macs.

andsnpl avatar Oct 22 '24 04:10 andsnpl

@andsnpl: ahhh yes, good find! I wonder if it's worth filing a PR to update the default exec env variables to add /usr/local/bin to the path?

gservat avatar Oct 22 '24 12:10 gservat

I have the following script (defined as on-ws-change.sh and executable) to move Chrome PiP window:

#!/usr/bin/env sh

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

chrome_pip=$(printf '%s\n' $all_wins | rg 'Picture in Picture')
target_mon=$(printf '%s\n' $all_ws | rg "$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local win="$1"

  [[ -n $win ]] || return 0

  local win_mon=$(printf $win | cut -d'|' -f4 | xargs)
  local win_id=$(printf $win | cut -d'|' -f1 | xargs)
  local win_app=$(printf $win | cut -d'|' -f2 | xargs)
  local win_ws=$(printf $win | cut -d'|' -f5 | xargs)

  [[ $target_mon != $win_mon ]] && return 0
  [[ $ws == $win_ws ]] && return 0

  aerospace move-node-to-workspace --window-id $win_id $ws
}

move_win "${chrome_pip}"

And in my aerospace.toml, I have this:

exec-on-workspace-change = ['<full-path-to>/on-ws-change.sh']

farzadmf avatar Oct 22 '24 14:10 farzadmf

It looks like pip is a very common use case, safari pip seems to work out of box (perhaps it is due to the script I installed to fix zen browser pip?), would be nice if this is buildin instead

pencilcheck avatar Nov 05 '24 15:11 pencilcheck

@farzadmf Your code breaks when I have multiple monitors connected (1 or 2), and I'm trying to switch to workspace named 1 or 2.

A quick fix would be to anchor the rg regex at the beginning of the string, when calculating the variable target_mon.

target_mon=$(printf '%s\n' $all_ws | rg "^$ws" | cut -d'|' -f2 | xargs)

Adding it here as reference in case there's anyone else trying to troubleshoot this.

thalesmello avatar Dec 04 '24 00:12 thalesmello

Oh cool, thanks for letting me know @thalesmello ; I didn't even think that someone would be using my code 😆

farzadmf avatar Dec 04 '24 05:12 farzadmf

I've updated @farzadmf 's script with support for different pip window titles. I tried to get it to support multple pip windows at once but no luck yet.

#!/usr/bin/env sh
# This seems to only work a single pip window at a time for now

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

# Array of possible window titles
pip_titles=("Picture-in-picture" "Picture-in-Picture" "Picture in Picture" "Picture in picture")

# Function to find matching PIP windows
find_pip_windows() {
  local titles=("$@")
  local result=""
  for title in "${titles[@]}"; do
    local matches=$(printf '%s\n' "$all_wins" | rg "$title")
    result="$result"$'\n'"$matches"
  done
  echo "$result" | sed '/^\s*$/d' # Remove empty lines
}

pip_wins=$(find_pip_windows "${pip_titles[@]}")
target_mon=$(printf '%s\n' "$all_ws" | rg "^$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local win="$1"

  [[ -n $win ]] || return 0

  local win_mon=$(echo "$win" | cut -d'|' -f4 | xargs)
  local win_id=$(echo "$win" | cut -d'|' -f1 | xargs)
  local win_app=$(echo "$win" | cut -d'|' -f2 | xargs)
  local win_ws=$(echo "$win" | cut -d'|' -f5 | xargs)

  # Skip if the monitor is already the target monitor or if the workspace matches
  [[ $target_mon != "$win_mon" ]] && return 0
  [[ $ws == "$win_ws" ]] && return 0

  aerospace move-node-to-workspace --window-id "$win_id" "$ws"
}

# Process each PIP window found
echo "$pip_wins" | while IFS= read -r win; do
  move_win "$win"
done

yougotwill avatar Dec 21 '24 08:12 yougotwill

Here's the edited version of the script that I use, for the use case of multiple window titles, if that's of any help

#!/usr/bin/env sh

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

target_mon=$(printf '%s\n' $all_ws | rg "^$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local filter="$1"
  local win="$(printf '%s\n' $all_wins | rg "$filter")"

  [[ -n $win ]] || return 0

  local win_mon=$(printf $win | cut -d'|' -f4 | xargs)
  local win_id=$(printf $win | cut -d'|' -f1 | xargs)
  local win_app=$(printf $win | cut -d'|' -f2 | xargs)
  local win_ws=$(printf $win | cut -d'|' -f5 | xargs)

  [[ $target_mon != $win_mon ]] && return 0
  [[ $ws == $win_ws ]] && return 0

  aerospace move-node-to-workspace --window-id $win_id $ws
}

# YouTube Picture in Picture
move_win 'Picture in Picture'

# Google Meet Window
move_win 'about:blank - [Your Chrome Profile]'

thalesmello avatar Dec 21 '24 17:12 thalesmello

I have the following script (defined as on-ws-change.sh and executable) to move Chrome PiP window:

#!/usr/bin/env sh

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

chrome_pip=$(printf '%s\n' $all_wins | rg 'Picture in Picture')
target_mon=$(printf '%s\n' $all_ws | rg "$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local win="$1"

  [[ -n $win ]] || return 0

  local win_mon=$(printf $win | cut -d'|' -f4 | xargs)
  local win_id=$(printf $win | cut -d'|' -f1 | xargs)
  local win_app=$(printf $win | cut -d'|' -f2 | xargs)
  local win_ws=$(printf $win | cut -d'|' -f5 | xargs)

  [[ $target_mon != $win_mon ]] && return 0
  [[ $ws == $win_ws ]] && return 0

  aerospace move-node-to-workspace --window-id $win_id $ws
}

move_win "${chrome_pip}"

And in my aerospace.toml, I have this:

exec-on-workspace-change = ['<full-path-to>/on-ws-change.sh']

This works well for me. Thank you. 👍

Albert26193 avatar Dec 22 '24 08:12 Albert26193

I've updated @farzadmf 's script with support for different pip window titles. I tried to get it to support multple pip windows at once but no luck yet.

#!/usr/bin/env sh
# This seems to only work a single pip window at a time for now

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

# Array of possible window titles
pip_titles=("Picture-in-picture" "Picture-in-Picture" "Picture in Picture" "Picture in picture")

# Function to find matching PIP windows
find_pip_windows() {
  local titles=("$@")
  local result=""
  for title in "${titles[@]}"; do
    local matches=$(printf '%s\n' "$all_wins" | rg "$title")
    result="$result"$'\n'"$matches"
  done
  echo "$result" | sed '/^\s*$/d' # Remove empty lines
}

pip_wins=$(find_pip_windows "${pip_titles[@]}")
target_mon=$(printf '%s\n' "$all_ws" | rg "^$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local win="$1"

  [[ -n $win ]] || return 0

  local win_mon=$(echo "$win" | cut -d'|' -f4 | xargs)
  local win_id=$(echo "$win" | cut -d'|' -f1 | xargs)
  local win_app=$(echo "$win" | cut -d'|' -f2 | xargs)
  local win_ws=$(echo "$win" | cut -d'|' -f5 | xargs)

  # Skip if the monitor is already the target monitor or if the workspace matches
  [[ $target_mon != "$win_mon" ]] && return 0
  [[ $ws == "$win_ws" ]] && return 0

  aerospace move-node-to-workspace --window-id "$win_id" "$ws"
}

# Process each PIP window found
echo "$pip_wins" | while IFS= read -r win; do
  move_win "$win"
done

I took this to ChatGPT, and apparently the issue is the echo | while loop construct, which pipes each iteration to a subshell, so the script's state isn't preserved across iterations. This can be easily fixed by replacing with a for loop:

for win in $pip_wins; do
  move_win "$win"
done

Now all PIP windows matching the titles in pip_titles will be moved on workspace focus change.

gormanity avatar Dec 29 '24 04:12 gormanity

I've updated @farzadmf 's script with support for different pip window titles. I tried to get it to support multple pip windows at once but no luck yet.

#!/usr/bin/env sh
# This seems to only work a single pip window at a time for now

ws=${1:-$AEROSPACE_FOCUSED_WORKSPACE}

IFS=$'\n' all_wins=$(aerospace list-windows --all --format '%{window-id}|%{app-name}|%{window-title}|%{monitor-id}|%{workspace}')
IFS=$'\n' all_ws=$(aerospace list-workspaces --all --format '%{workspace}|%{monitor-id}')

# Array of possible window titles
pip_titles=("Picture-in-picture" "Picture-in-Picture" "Picture in Picture" "Picture in picture")

# Function to find matching PIP windows
find_pip_windows() {
  local titles=("$@")
  local result=""
  for title in "${titles[@]}"; do
    local matches=$(printf '%s\n' "$all_wins" | rg "$title")
    result="$result"$'\n'"$matches"
  done
  echo "$result" | sed '/^\s*$/d' # Remove empty lines
}

pip_wins=$(find_pip_windows "${pip_titles[@]}")
target_mon=$(printf '%s\n' "$all_ws" | rg "^$ws" | cut -d'|' -f2 | xargs)

move_win() {
  local win="$1"

  [[ -n $win ]] || return 0

  local win_mon=$(echo "$win" | cut -d'|' -f4 | xargs)
  local win_id=$(echo "$win" | cut -d'|' -f1 | xargs)
  local win_app=$(echo "$win" | cut -d'|' -f2 | xargs)
  local win_ws=$(echo "$win" | cut -d'|' -f5 | xargs)

  # Skip if the monitor is already the target monitor or if the workspace matches
  [[ $target_mon != "$win_mon" ]] && return 0
  [[ $ws == "$win_ws" ]] && return 0

  aerospace move-node-to-workspace --window-id "$win_id" "$ws"
}

# Process each PIP window found
echo "$pip_wins" | while IFS= read -r win; do
  move_win "$win"
done

I took this to ChatGPT, and apparently the issue is the echo | while loop construct, which pipes each iteration to a subshell, so the script's state isn't preserved across iterations. This can be easily fixed by replacing with a for loop:

for win in $pip_wins; do
  move_win "$win"
done

Now all PIP windows matching the titles in pip_titles will be moved on workspace focus change.

Nice it works! Thank you so much.

yougotwill avatar Dec 29 '24 11:12 yougotwill

Can't make this work with Sequoia. PIP stays in the same space

roman-c-e avatar Feb 01 '25 09:02 roman-c-e

Can't make this work with Sequoia. PIP stays in the same space

works on mac processor...and not on intel for some reason

roman-c-e avatar Feb 04 '25 07:02 roman-c-e

Any update on this? I feel like this should be labeled a bug as it prevents PIP from behaving like PIP. Hyprland and i3 do this out of box.

qudo-code avatar Feb 11 '25 02:02 qudo-code