hyprland-community / pyprland

Scratchpads & many goodies for Hyprland [maintainer=@fdev31]
MIT License
390 stars 17 forks source link

[FEAT] fetch_client_menu - add binding for sending window back to its orignal workspace #60

Closed luravoid closed 7 months ago

luravoid commented 8 months ago

Hi, I have very similar functionality in awesomewm to fetch_client_menu but additional to moving windows with rofi I have also biding for sending windows back. With it I can toggle every open window seamlessly no matter what monitor or tag I am on. I would love to have something similar on hyprland and I think that in general this is a very useful functionality.

My approach involves capturing the original tag/workspace of a window when it's moved using rofi. Then this stored location is used in function for sending window back to its previous tag.

Here is my code from my awesome config for inspiration:

local awful = require("awful")
local gears = require("gears")

local move_return = {}
local moved_windows = {}

function move_return.move_window()
    local cmd = "echo -e \""
    for _, c in ipairs(client.get()) do
        if c.name and c.class then
            local window_label = c.class .. "  ✰  " .. c.name
            cmd = cmd .. window_label .. "\\n"
        end
    end
    cmd = cmd .. "\" | rofi -dmenu -i -p \"Window:\" -theme ~/.config/rofi/themes/launcher.rasi"

    awful.spawn.easy_async_with_shell(cmd, function(stdout)
        local selection = stdout:gsub("\n", "")
        if selection == "" then return end

        for _, c in ipairs(client.get()) do
            if c.class .. "  ✰  " .. c.name == selection then
                local s = awful.screen.focused()
                local original_tag = c.first_tag
                c:move_to_tag(s.selected_tag)
                c:raise()
                client.focus = c
                moved_windows[c.window] = original_tag 
                return
            end
        end
    end)
end

function move_return.send_window_back()
    local focused_client = client.focus
    if focused_client and moved_windows[focused_client.window] then
        local original_tag = moved_windows[focused_client.window]
        focused_client:move_to_tag(original_tag)
        moved_windows[focused_client.window] = nil

        gears.timer.delayed_call(function()
            if #awful.screen.focused().clients > 0 then
                client.focus = awful.screen.focused().clients[1]
            else
                client.focus = nil
            end
        end)
    end
end

return move_return

--[[ Keybindings

awful.key({ modkey }, "e", move_return.move_window,
    { description = "select and move window to current tag", group = "utils" }),

awful.key({ modkey, "Shift" }, "e", move_return.send_window_back,
    { description = "send window back to its original tag", group = "utils" }),

--]]
fdev31 commented 8 months ago

This looks a bit fragile, what if the user manipulates the window without using the plugin ? It should reset I guess... it would only work well for very controlled sequences... but maybe that's fine. I may have a look at it but with a low priority.

luravoid commented 8 months ago

If the user manipulates the window, such as moving it to another workspace without using a plugin, then I think it doesn't matter. The saved workspace location will only reset the next time using fetch-client-menu or when window is sent back. So even after manipulating the window without using the plugin, the window will still return to the same place from which it was moved, if the keybinding was used. User just won't send the window back if he doesn't need it.

Please take a look at my script that I created today and see how I implemented saving workspace of the window before moving. This script kinda replicate dwm/awesome tags behaviour so I can very easily toggle windows from all the workspaces into the single view.

#!/bin/bash

if [ $# -eq 0 ]; then
    echo "Usage: $0 <window_class>"
    exit 1
fi

WINDOW_CLASS="$1"

STORE_FILE="$HOME/.config/hypr/.win_store"

if [ ! -f "$STORE_FILE" ]; then
    touch "$STORE_FILE"
fi

CURRENT_WORKSPACE=$(hyprctl activeworkspace -j | jq '.id')

WINDOWS_INFO=$(hyprctl -j clients | jq --arg CLASS "$WINDOW_CLASS" -c '.[] | select(.class == $CLASS)')

echo "$WINDOWS_INFO" | while IFS= read -r WINDOW; do
    WINDOW_ADDRESS=$(echo "$WINDOW" | jq -r '.address')
    WINDOW_WORKSPACE=$(echo "$WINDOW" | jq '.workspace.id')

    if grep -q "$WINDOW_ADDRESS" "$STORE_FILE"; then
        ORIGINAL_WORKSPACE=$(grep "$WINDOW_ADDRESS" "$STORE_FILE" | cut -d ' ' -f 2)
        if [ "$WINDOW_WORKSPACE" -eq "$CURRENT_WORKSPACE" ]; then
            hyprctl dispatch movetoworkspacesilent "$ORIGINAL_WORKSPACE","address:$WINDOW_ADDRESS"
            sed -i "/$WINDOW_ADDRESS/d" "$STORE_FILE"
        else
            hyprctl dispatch movetoworkspace "$CURRENT_WORKSPACE","address:$WINDOW_ADDRESS"
        fi
    else
        echo "$WINDOW_ADDRESS $WINDOW_WORKSPACE" >> "$STORE_FILE"
        hyprctl dispatch movetoworkspace "$CURRENT_WORKSPACE","address:$WINDOW_ADDRESS"
    fi
done

And my keybindings:

bind = $mainMod CTRL, 1, exec, ~/.config/hypr/scripts/toggle-window neovim
bind = $mainMod CTRL, 2, exec, ~/.config/hypr/scripts/toggle-window Brave-browser
bind = $mainMod CTRL, 3, exec, ~/.config/hypr/scripts/toggle-window VSCodium
bind = $mainMod CTRL, 4, exec, ~/.config/hypr/scripts/toggle-window discord
bind = $mainMod CTRL, 5, exec, ~/.config/hypr/scripts/toggle-window obsidian
fdev31 commented 7 months ago

Are you able to test the git version to see if you are happy with the "unfetch_client" command ?

luravoid commented 7 months ago

Works great. Thank you