diff --git a/README.md b/README.md index c50cbc9..eb1572f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ A single config file `~/.config/hypr/pyprland.json` is used, using the following - `scratchpad` implements dropdowns & togglable poppups - `monitors` allows relative placement of monitors depending on the model - `workspaces_follow_focus` provides commands and handlers allowing a more flexible workspaces usage on multi-monitor setups. If you think the multi-screen behavior of hyprland is not usable or broken/unexpected, this is probably for you. +- `lost_windows` brings lost floating windows to the current workspace ## Installation @@ -83,6 +84,12 @@ Create a configuration file in `~/.config/hypr/pyprland.json` enabling a list of # Configuring plugins +## `lost_windows` plugin + +### Command + +- `attract_lost`: brings the lost windows to the current screen / workspace + ## `monitors` plugin Requires `wlr-randr`. @@ -91,6 +98,7 @@ Allows relative placement of monitors depending on the model ("description" retu ### Configuration + #### `placement` Supported placements are: @@ -111,7 +119,7 @@ If set, runs the associated command for screens which aren't matching any of the Make non-visible workspaces follow the focused monitor. Also provides commands to switch between workspaces wile preserving the current monitor assignments: -### Commands +### Command - `change_workspace` ``: changes the workspace of the focused monitor @@ -171,7 +179,7 @@ Then in the configuration file, add something like this: And you'll be able to toggle pavucontrol with MOD + V. -### Command-line options +### Commands - `toggle ` : toggle the given scratchpad - `show ` : show the given scratchpad diff --git a/pyprland/plugins/lost_windows.py b/pyprland/plugins/lost_windows.py new file mode 100644 index 0000000..6f9b559 --- /dev/null +++ b/pyprland/plugins/lost_windows.py @@ -0,0 +1,41 @@ +from .interface import Plugin + +from ..ipc import hyprctlJSON, hyprctl + + +def contains(monitor, window): + if not ( + window["at"][0] > monitor["x"] + and window["at"][0] < monitor["x"] + monitor["width"] + ): + return False + if not ( + window["at"][1] > monitor["y"] + and window["at"][1] < monitor["y"] + monitor["height"] + ): + return False + return True + + +class Extension(Plugin): + async def run_attract_lost(self, *args): + monitors = await hyprctlJSON("monitors") + windows = await hyprctlJSON("clients") + lost = [ + win + for win in windows + if win["floating"] and not any(contains(mon, win) for mon in monitors) + ] + focused = [mon for mon in monitors if mon["focused"]][0] + interval = focused["width"] / (1 + len(lost)) + intervalY = focused["height"] / (1 + len(lost)) + batch = [] + workspace: int = focused["activeWorkspace"]["id"] + margin = interval // 2 + marginY = intervalY // 2 + for i, window in enumerate(lost): + batch.append(f'movetoworkspacesilent {workspace},pid:{window["pid"]}') + batch.append( + f'movewindowpixel exact {int(margin + focused["x"] + i*interval)} {int(marginY + focused["y"] + i*intervalY)},pid:{window["pid"]}' + ) + await hyprctl(batch)