Skip to content

Instantly share code, notes, and snippets.

@haselwarter
Created September 5, 2021 11:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haselwarter/5e1b16bf6288a4a0376ca6480da4d6d6 to your computer and use it in GitHub Desktop.
Save haselwarter/5e1b16bf6288a4a0376ca6480da4d6d6 to your computer and use it in GitHub Desktop.
#!/usr/bin/bash
# Goal: move workspace $1 to the currently focused output, move the workspace on the current output
# to the output where $1 was.
# The workspace we want to end up on
dest_ws=$1
# The output where the destination ws resides
output_of_dest_ws=$(i3-msg -t get_workspaces | jq .[] | jq -r 'select(.name == "'$dest_ws'").output')
# The currently focused output
current_output=$(i3-msg -t get_workspaces | jq .[] | jq -r "select(.focused == true).output")
# The workspace on the current output
current_ws=$(i3-msg -t get_workspaces | jq .[] | jq -r "select(.focused == true).name")
# Send away the current workspace
i3-msg move workspace to output $output_of_dest_ws
# Select the destination workspace
i3-msg workspace $dest_ws
# Move the destination workspace to what was the current output in the beginning
i3-msg move workspace to output $current_output
# Re-activate the destination workspace
# sleep 0.2
i3-msg workspace $dest_ws
@totoro-ghost
Copy link

i3-msg [workspace=$current_ws] move workspace to output $output_of_dest_ws
i3-msg [workspace=$dest_ws] move workspace to output $current_output
i3-msg workspace $dest_ws

how about this?

@haselwarter
Copy link
Author

Suffers from the same race condition as far as I can tell. It'll work about 80% of the time but every once in a while the last message will be processed too soon and $dest_ws ends up not being selected.
It's nice to save an invocation of i3-msg, I couldn't find the [workspace=...] syntax in the i3 documentation.

@budRich
Copy link

budRich commented Sep 8, 2021

#!/bin/bash

dest_ws=$1
json=$(i3-msg -t get_workspaces)

output_of_dest_ws=$(jq -r ".[] | select(.name == \"$dest_ws\").output" <<< "$json")
[[ $output_of_dest_ws ]] \
  || { echo "could not find dest_ws: $dest_ws" ; exit ;}

current_output=$(jq -r ".[] | select(.focused == true).output" <<< "$json")

[[ $output_of_dest_ws = "$current_output" ]] || {
  msg+="move workspace to output $output_of_dest_ws;"
  msg+="[workspace=$dest_ws] move workspace to output $current_output;"
}

msg+="workspace $dest_ws"
i3-msg "$msg"

one i3-msg, two (or one) jq, re-use the json, trims the time a bit. but jq is s l o w...

@budRich
Copy link

budRich commented Sep 8, 2021

0 jq pure bash version, gotta go fast:

#!/bin/bash

dest_ws=$1
json=$(i3-msg -t get_workspaces)

dest_re='"name":"'"$dest_ws"'[^}]+},"output":"([^"]+)'
current_re='"focused":true[^}]+},"output":"([^"]+)'

[[ $json =~ $current_re ]] && current_output=${BASH_REMATCH[1]}

if [[ $json =~ $dest_re ]]; then
  output_of_dest_ws=${BASH_REMATCH[1]}

  [[ $output_of_dest_ws = "$current_output" ]] || {
    msg+="move workspace to output $output_of_dest_ws;"
    msg+="[workspace=$dest_ws] move workspace to output $current_output;"
  }

  msg+="workspace $dest_ws"
else
  msg+="workspace $dest_ws;"
  msg+="[workspace=$dest_ws] move workspace to output $current_output;"
fi

i3-msg "$msg"

edit: script will now create the dest_ws if it doesn't exist and if it isn't on current_output (it was assigned to a different output etc) it will still be moved to the current one.

@budRich
Copy link

budRich commented Sep 11, 2021

i refactored and annotated my version:

#!/bin/bash

# Switches workspaces in i3 like xmonad.
# Always bring the requested workspace to the
# focused monitor ("output" in i3's terminology).
# If the requested workspace is on another monitor,
# the current workspace and requested workspace 
# will swap positions with each other.

# ~/.config/i3/config
# set $ws1 "1"
#
# from this:
# bindsym $mod+1 workspace number $ws1
#
# to this:
# bindsym $mod+1 exec --no-startup-id wsmove $ws1
#
# (assuming this script is named 'wsmove' and available in $PATH)
# -----------------------------------------------

declare ws_target  # $1 == target workspace
declare op_current # current output
declare op_target  # target output
declare re_target  # regex to get op_target
declare re_current # regex to get op_current
declare msg        # commands passed to i3-msg

ws_target=$1

[[ $ws_target ]] || {
  echo "usage: ${0##*/} WORKSPACE" >&2
  exit 1
}

json=$(i3-msg -t get_workspaces)

re_target='"name":"'"$ws_target"'[^}]+},"output":"([^"]+)'
re_current='"focused":true[^}]+},"output":"([^"]+)'

[[ $json =~ $re_current ]] && op_current=${BASH_REMATCH[1]}

if [[ $json =~ $re_target ]]; then
  op_target=${BASH_REMATCH[1]}

  # only swap monitors if the workspaces are on 
  # different outputs
  [[ $op_target = "$op_current" ]] || {
    # move the current workspace to op_target
    # we don't need to know the current workspace 
    # name for this to work.
    msg+="move workspace to output $op_target;"
    msg+="[workspace=$ws_target] move workspace to output $op_current;"
  }

else # ws_target doesn't exist
  # create ws_target
  msg+="workspace --no-auto-back-and-forth $ws_target;"
  # move ws_target to op_current in case it was
  # "assigned" to a different output. swapping 
  # outputs is probably not what we want
  msg+="[workspace=$ws_target] move workspace to output $op_current;"
fi

# always focus ws_target
msg+="workspace --no-auto-back-and-forth $ws_target"
i3-msg "$msg"

@totoro-ghost
Copy link

totoro-ghost commented Sep 12, 2021

so i found this https://github.com/altdesktop/i3ipc-python and this works perfectly for one of my scripts, this swapping one

#!/usr/bin/env python3
# Swap visible workspace on two monitors

import i3ipc
import sys

# Create the Connection object that can be used to send commands and subscribe
# to events.
i3 = i3ipc.Connection()

workspaces = i3.get_workspaces()

focused = None
notfocused = None

for workspace in workspaces:
    if workspace.visible and workspace.focused :
        focused = workspace
    elif workspace.visible:
        notfocused = workspace

# checking if any of the workspace is not availiable, in case of single monitor
if notfocused == None or focused == None:
    sys.exit(-1)

command1 = "[workspace=" + notfocused.name + "] " + "move --no-auto-back-and-forth workspace to output " + focused.output
command2 = "[workspace=" + focused.name + "] " + "move --no-auto-back-and-forth workspace to output " + notfocused.output

command1_ret = i3.command(command1)

if not command1_ret[0].success:
    print(command1_ret[0].error)
    sys.exit(-1)

command2_ret = i3.command(command2)

if not command2_ret[0].success:
    print(command1_ret[0].error)
    sys.exit(-2)

command3 = "workspace " + notfocused.name 
command4 = "workspace " + focused.name

command3_ret = i3.command(command3)

if not command3_ret[0].success:
    print(command3_ret[0].error)
    sys.exit(-1)

command4_ret = i3.command(command4)

if not command4_ret[0].success:
    print(command4_ret[0].error)
    sys.exit(-1)

i got no error, i ran it nearly 50 times, in dfferent cases, so you can make something like this for your case.

@budRich
Copy link

budRich commented Sep 12, 2021

that python script takes ~300ms to execute on my machine while my bash version takes ~40ms. Mine also does smarter tests, like why is the python script moving the target ws and judging by the result of the command if it was possible, it is easy to do without issuing a command. The bash script also has no dependencies, besides bash and i3-msg.

@totoro-ghost
Copy link

totoro-ghost commented Sep 12, 2021

@budRich can you help me convert my python script to bash too, it is for following case,
i have two monitors, and when i press a keybind
i want to move workspace on unfocused output to focused one and
the workspace on focused output to unfoucsed one

like why is the python script moving the target ws and judging by the result of the command if it was possible

i gave my script for swapping, it's not same as yours, i was just giving a example of how i use the ipc-python

this is the script which works same as your one, ans yes it is slow than the bash one, that's why i want to convert my one to the bash one

#!/usr/bin/env python3

# Switches workspaces in i3 like xmonad.
# Always bring the requested workspace to the
# focused monitor ("output" in i3's terminology).
# If the requested workspace is on another monitor,
# the current workspace and requested workspace 
# will swap positions with each other.

import i3ipc
import sys

# Create the Connection object that can be used to send commands and subscribe
# to events.
i3 = i3ipc.Connection()

ws_target = None
target = None     # target workspace
focused = None    # currently focused workspace
ws_target=sys.argv[1] # $1 == target workspace
workspaces = i3.get_workspaces()
for workspace in workspaces:
    if workspace.name == ws_target:
        target = workspace
    if workspace.visible and workspace.focused:
        focused = workspace
    if target and focused:
        break

if target == None: 
    # target doesn't exist
    command1 = "workspace " + ws_target
    command1_ret = i3.command(command1)
elif target.output != focused.output:
    # only swap monitors if the workspaces are on different outputs
    command1 = "[workspace=" + target.name + "] move workspace to output " + focused.output
    command2 = "[workspace=" + focused.name + "] move workspace to output " + target.output
    command1_ret = i3.command(command1)
    command2_ret = i3.command(command2)
    command3 = "workspace " + focused.name 
    command4 = "workspace " + target.name
    command3_ret = i3.command(command3)
    command4_ret = i3.command(command4)
else:
    # target workspace on same monitor
    command1 = "workspace --no-auto-back-and-forth " + target.name
    command1_ret = i3.command(command1)

@budRich
Copy link

budRich commented Sep 12, 2021

@totoro-ghost Yeah I think i can brew up that monitor ws swapper thing in bash. Sounds useful and more sober then the xmonad workspace summoning shenanigans :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment