Skip to content

Instantly share code, notes, and snippets.

@dedsm
Created August 23, 2022 19:09
Show Gist options
  • Save dedsm/610a004d896cac3a93c59558a8326dd9 to your computer and use it in GitHub Desktop.
Save dedsm/610a004d896cac3a93c59558a8326dd9 to your computer and use it in GitHub Desktop.
This gist is a helper using i3 ipc that makes i3 behave like xmonad in some workspace-related bindings Mod+{w,e,r} and sharing workspaces across all active displays
import argparse
from i3ipc import Connection
sway = Connection()
outputs = [o for o in sway.get_outputs() if o.active]
def focus_monitor(args):
indices = {"left": 0, "center": 1, "right": 2}
orientation = indices[args.orientation]
if len(outputs) <= 1 or len(outputs) < orientation+1:
print(len(outputs))
return
sorted_outputs = sorted(outputs, key=lambda x: x.rect.x)
output = sorted_outputs[orientation]
sway.command(f"workspace {output.current_workspace}")
def send_to_monitor(args):
indices = {"left": 0, "center": 1, "right": 2}
orientation = indices[args.orientation]
if len(outputs) <= 1 or len(outputs) < orientation+1:
print(len(outputs))
return
sorted_outputs = sorted(outputs, key=lambda x: x.rect.x)
output = sorted_outputs[orientation]
sway.command(f"move container to workspace {output.current_workspace}")
def switch_workspace(args):
workspaces = sway.get_workspaces()
workspaces_by_name = {w.name: w for w in workspaces}
focused_workspace = next((w for w in workspaces if w.focused))
destination = args.workspace
if focused_workspace.name == destination:
return
if destination not in workspaces_by_name:
# workspace doesn't exist
sway.command(f"workspace {destination}")
return
target_workspace = workspaces_by_name[destination]
destination_output = focused_workspace.output
source_output = target_workspace.output
if destination_output == source_output:
sway.command(f"workspace {destination}")
return
# workspace belongs to a different output
if target_workspace.visible:
# the workspace we want in the current output is
# being displayed in another output, so we swap
commands = [
"workspace ___temp___",
f"move workspace to output {source_output}",
f"workspace {destination}",
f"move workspace to output {destination_output}",
f"workspace {focused_workspace.name}",
f"move workspace to output {source_output}",
f"workspace {destination}",
]
command = ";".join(commands)
sway.command(command)
else:
# the workspace is in a different output but hidden
# bring it to the destination output, no need for temp
# since we know there's at least one more
commands = [
f"workspace {destination}",
f"move workspace to output {destination_output}"
]
command = ";".join(commands)
sway.command(command)
parser = argparse.ArgumentParser(description="Process custom sway commands")
subparsers = parser.add_subparsers(help="sub-command help")
output_choose = subparsers.add_parser(
"monitor",
help="pick monitor in X orientation order"
)
output_choose.add_argument(
"orientation",
type=str,
choices=["left", "center", "right"]
)
output_choose.set_defaults(func=focus_monitor)
send_to_output = subparsers.add_parser(
"send_to_output",
help="pick monitor in X orientation order"
)
send_to_output.add_argument(
"orientation",
type=str,
choices=["left", "center", "right"]
)
send_to_output.set_defaults(func=send_to_monitor)
workspace_choose = subparsers.add_parser(
"workspace",
help="switch workspace"
)
workspace_choose.add_argument(
"workspace",
type=str
)
workspace_choose.set_defaults(func=switch_workspace)
args = parser.parse_args()
args.func(args)
@apirogov
Copy link

apirogov commented Nov 1, 2024

Thanks a lot, this is perfect! I finally gave up resistance and moved from X to wayland and from xmonad to sway. This script was the last missing piece I needed to be happy with the move :)

@dedsm
Copy link
Author

dedsm commented Nov 1, 2024

Thanks a lot, this is perfect! I finally gave up resistance and moved from X to wayland and from xmonad to sway. This script was the last missing piece I needed to be happy with the move :)

I'm glad it could help someone @apirogov :)

@Schievel1
Copy link

Schievel1 commented Dec 28, 2024

Hey, I wrote the same thing for hyprland (https://github.com/hyprwm/contrib/blob/main/try_swap_workspace/try_swap_workspace). I have the same user story as you do, coming from Xmonad to wayland. I am having trouble with hyprlands dpms and therefore went to sway.

Although maybe I am mistaken but Xmonad has a way to send a window one output to the left/right. So when the window is on the left output, sending it one output to the right means it ends of on the center screen. Doing it again would send it to the right screen.

// edit: I hacked this together real quick:

#!/usr/bin/env python
import argparse
from i3ipc import Connection

sway = Connection()
outputs = [o for o in sway.get_outputs() if o.active]


def focus_monitor(args):
    indices = {"left": 0, "center": 1, "right": 2}
    orientation = indices[args.orientation]
    if len(outputs) <= 1 or len(outputs) < orientation+1:
        print(len(outputs))
        return

    sorted_outputs = sorted(outputs, key=lambda x: x.rect.x)
    output = sorted_outputs[orientation]

    sway.command(f"workspace {output.current_workspace}")


def send_to_monitor(args):
    indices = {"left": 0, "center": 1, "right": 2}
    orientation = indices[args.orientation]
    if len(outputs) <= 1 or len(outputs) < orientation+1:
        print(len(outputs))
        return

    sorted_outputs = sorted(outputs, key=lambda x: x.rect.x)
    output = sorted_outputs[orientation]

    sway.command(f"move container to workspace {output.current_workspace}")

def send_to_direction(args):
    indices = {"left": -1, "right": 1}
    direction = indices[args.direction]

    workspaces = sway.get_workspaces()
    focused_workspace = next((w for w in workspaces if w.focused))
    focused_output = next((o for o in outputs if o.focused))
    sorted_outputs = sorted(outputs, key=lambda x: x.rect.x)

    target_output_idx = -1
    for idx in range(len(sorted_outputs)):
        o = sorted_outputs[idx]
        if o == focused_output:
            if idx + direction > len(outputs)-1:
                target_output_idx = 0
            elif (idx + direction < 0):
                target_output_idx = len(outputs)-1
            else:
                target_output_idx = idx + direction
            break

    target_output = sorted_outputs[target_output_idx]
    sway.command(f"move container to workspace {target_output.current_workspace}")

def switch_workspace(args):
    workspaces = sway.get_workspaces()
    workspaces_by_name = {w.name: w for w in workspaces}
    focused_workspace = next((w for w in workspaces if w.focused))
    destination = args.workspace

    if focused_workspace.name == destination:
        return

    if destination not in workspaces_by_name:
        # workspace doesn't exist
        sway.command(f"workspace {destination}")
        return

    target_workspace = workspaces_by_name[destination]
    destination_output = focused_workspace.output
    source_output = target_workspace.output
    if destination_output == source_output:
        sway.command(f"workspace {destination}")
        return

    # workspace belongs to a different output
    if target_workspace.visible:
        # the workspace we want in the current output is
        # being displayed in another output, so we swap
        commands = [
            "workspace ___temp___",
            f"move workspace to output {source_output}",
            f"workspace {destination}",
            f"move workspace to output {destination_output}",
            f"workspace {focused_workspace.name}",
            f"move workspace to output {source_output}",
            f"workspace {destination}",
        ]
        command = ";".join(commands)
        sway.command(command)
    else:
        # the workspace is in a different output but hidden
        # bring it to the destination output, no need for temp
        # since we know there's at least one more
        commands = [
            f"workspace {destination}",
            f"move workspace to output {destination_output}"
        ]
        command = ";".join(commands)
        sway.command(command)


parser = argparse.ArgumentParser(description="Process custom sway commands")

subparsers = parser.add_subparsers(help="sub-command help")

output_choose = subparsers.add_parser(
    "monitor",
    help="pick monitor in X orientation order"
)
output_choose.add_argument(
    "orientation",
    type=str,
    choices=["left", "center", "right"]
)
output_choose.set_defaults(func=focus_monitor)

send_to_output = subparsers.add_parser(
    "send_to_output",
    help="pick monitor in X orientation order"
)
send_to_output.add_argument(
    "orientation",
    type=str,
    choices=["left", "center", "right"]
)
send_to_output.set_defaults(func=send_to_monitor)

send_to_dir = subparsers.add_parser(
    "send_to_direction",
    help="pick monitor in X orientation order"
)
send_to_dir.add_argument(
    "direction",
    type=str,
    choices=["left", "right"]
)
send_to_dir.set_defaults(func=send_to_direction)

workspace_choose = subparsers.add_parser(
    "workspace",
    help="switch workspace"
)
workspace_choose.add_argument(
    "workspace",
    type=str
)
workspace_choose.set_defaults(func=switch_workspace)


args = parser.parse_args()
args.func(args)

This does the movement to the left/ right monitor to the currently focused monitor. I don't know python, so that is pretty rough and done in a C way I guess.
Usage is

bindsym Control+comma exec ~/.config/sway/i3xmonadhelper send_to_direction left
bindsym Control+period exec ~/.config/sway/i3xmonadhelper send_to_direction right

(change path accordingly)

@dedsm
Copy link
Author

dedsm commented Dec 28, 2024

@Schievel1 I never used that with xmonad (sending to the right/left) but maybe it has.

I also used something similar in hyprland but there was already a plugin for this, and later on they added it in hyprland itself, this is my hyprland config for similar functionality:

        "$mod, W, focusmonitor, l"
        "$mod, E, focusmonitor, r"
        "$mod SHIFT, W, movewindow, mon:l"
        "$mod SHIFT, E, movewindow, mon:r"

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