Skip to content

Commit 8287ff1

Browse files
authored
Add GrabRogueWindows dispatcher (#172)
Add GrabRogueWindows dispatcher to move windows from disconnected monitors to current monitor
1 parent a8e39ff commit 8287ff1

File tree

2 files changed

+51
-14
lines changed

2 files changed

+51
-14
lines changed

README.md

+14-12
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ A small plugin to provide `awesome`/`dwm`-like behavior with workspaces: split t
99
- Hyprland >= v0.38.1
1010

1111
# Installing
12-
Since Hyprland plugins don't have ABI guarantees, you *should* download the Hyprland source and compile it if you plan to use plugins.
13-
This ensures the compiler version is the same between the Hyprland build you're running, and the plugins you are using.
14-
15-
The guide on compiling and installing Hyprland manually is on the [wiki](http://wiki.hyprland.org/Getting-Started/Installation/#manual-manual-build)
1612

1713
## Using [hyprpm](https://wiki.hyprland.org/Plugins/Using-Plugins/#hyprpm)
1814
Hyprpm is a tool integrated with the latest Hyprland version, to use it first you'll need to add the repository and then enable the plugin
@@ -27,7 +23,12 @@ Add the following in your `hyprland.conf` file to automatically load the plugin
2723

2824
## Manual installation
2925

30-
1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo
26+
Since Hyprland plugins don't have ABI guarantees, you *should* download the Hyprland source and compile it if you plan to install plugins manually.
27+
This ensures the compiler version is the same between the Hyprland build you're running, and the plugins you are using.
28+
29+
The guide on compiling and installing Hyprland manually is on the [wiki](http://wiki.hyprland.org/Getting-Started/Installation/#manual-manual-build)
30+
31+
1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo, for example:
3132
- `export HYPRLAND_HEADERS="$HOME/repos/Hyprland"`
3233
2. Compile
3334
- `make all`
@@ -100,21 +101,22 @@ module will handle the rest.
100101

101102
# Usage
102103

103-
The plugin provides drop-in replacements for workspace-related commands
104+
The plugin provides drop-in replacements for workspace-related commands, to be able to easily specify the `n`th workspace on the focused monitor:
104105

105106
| Normal | Replacement |
106107
|-----------------------|-------------------------------|
107108
| workspace | split-workspace |
108109
| movetoworkspace | split-movetoworkspace |
109110
| movetoworkspacesilent | split-movetoworkspacesilent |
110111

111-
And two new ones, to move windows between monitors
112+
And these new commands:
112113

113-
| Normal | Arguments |
114-
|---------------------------|-------------------|
115-
| split-cycleworkspaces | next/prev/+1/-1 |
116-
| split-changemonitor | next/prev/+1/-1 |
117-
| split-changemonitorsilent | next/prev/+1/-1 |
114+
| Normal | Arguments | Description |
115+
|---------------------------|-------------------|-------------------------------------------------------------------------------------------|
116+
| split-cycleworkspaces | next/prev/+1/-1 | Cycle through workspaces on the current monitor |
117+
| split-changemonitor | next/prev/+1/-1 | Move a workspace to the next/previous monitor |
118+
| split-changemonitorsilent | next/prev/+1/-1 | Move a workspace to the next/previous monitor without focus change |
119+
| split-grabroguewindows | | After disconnecting a monitor, call this to move all rogue windows to the current monitor |
118120

119121
It also provides the following config values
120122
| Name | Type | Default | Description |

src/main.cpp

+37-2
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,40 @@ SDispatchResult splitChangeMonitor(const std::string& value)
216216
return changeMonitor(false, value);
217217
}
218218

219+
SDispatchResult grabRogueWindows(const std::string& /*unused*/)
220+
{
221+
// implementation loosely based on shezdy's hyprsplit: https://github.com/shezdy/hyprsplit
222+
Debug::log(INFO, "[split-monitor-workspaces] Grabbing rogue windows");
223+
const auto currentMonitor = getCurrentMonitor();
224+
if (currentMonitor == nullptr) {
225+
Debug::log(ERR, "[split-monitor-workspaces] No active monitor found");
226+
return {.success = false, .error = "No active monitor found"};
227+
}
228+
const auto currentWorkspace = currentMonitor->activeWorkspace;
229+
if (currentWorkspace == nullptr) {
230+
Debug::log(ERR, "[split-monitor-workspaces] No active workspace found");
231+
return {.success = false, .error = "No active workspace found"};
232+
}
233+
234+
for (const auto& window : g_pCompositor->m_vWindows) {
235+
// ignore unmapped and special windows
236+
if (!window->m_bIsMapped && !window->onSpecialWorkspace())
237+
continue;
238+
239+
auto const workspaceName = window->m_pWorkspace->m_szName;
240+
auto const monitorID = window->m_pMonitor->ID;
241+
242+
bool isInRogueWorkspace = !g_vMonitorWorkspaceMap.contains(monitorID) || // if the monitor is not mapped, the window is rogue
243+
!std::ranges::any_of(g_vMonitorWorkspaceMap[monitorID], [&workspaceName](const auto& mappedWorkspaceName) { return workspaceName == mappedWorkspaceName; });
244+
if (isInRogueWorkspace) {
245+
Debug::log(INFO, "[split-monitor-workspaces] Moving rogue window {} from workspace {} to workspace {}", window->m_szTitle.c_str(), workspaceName.c_str(),
246+
currentWorkspace->m_szName.c_str());
247+
g_pCompositor->moveWindowToWorkspaceSafe(window, currentWorkspace);
248+
}
249+
}
250+
return {.success = true, .error = ""};
251+
}
252+
219253
void mapMonitor(const PHLMONITOR& monitor) // NOLINT(readability-convert-member-functions-to-static)
220254
{
221255
if (monitor->activeMonitorRule.disabled) {
@@ -228,7 +262,7 @@ void mapMonitor(const PHLMONITOR& monitor) // NOLINT(readability-convert-member-
228262
return;
229263
}
230264

231-
int workspaceIndex = monitor->ID * g_workspaceCount + 1;
265+
int workspaceIndex = (monitor->ID * g_workspaceCount) + 1;
232266

233267
Debug::log(INFO, "{}",
234268
"[split-monitor-workspaces] Mapping workspaces " + std::to_string(workspaceIndex) + "-" + std::to_string(workspaceIndex + g_workspaceCount - 1) + " to monitor " + monitor->szName);
@@ -262,7 +296,7 @@ void mapMonitor(const PHLMONITOR& monitor) // NOLINT(readability-convert-member-
262296

263297
void unmapMonitor(const PHLMONITOR& monitor)
264298
{
265-
int workspaceIndex = monitor->ID * g_workspaceCount + 1;
299+
int workspaceIndex = (monitor->ID * g_workspaceCount) + 1;
266300

267301
Debug::log(INFO, "{}",
268302
"[split-monitor-workspaces] Unmapping workspaces " + std::to_string(workspaceIndex) + "-" + std::to_string(workspaceIndex + g_workspaceCount - 1) + " from monitor " + monitor->szName);
@@ -367,6 +401,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle)
367401
HyprlandAPI::addDispatcherV2(PHANDLE, "split-movetoworkspacesilent", splitMoveToWorkspaceSilent);
368402
HyprlandAPI::addDispatcherV2(PHANDLE, "split-changemonitor", splitChangeMonitor);
369403
HyprlandAPI::addDispatcherV2(PHANDLE, "split-changemonitorsilent", splitChangeMonitorSilent);
404+
HyprlandAPI::addDispatcherV2(PHANDLE, "split-grabroguewindows", grabRogueWindows);
370405

371406
// reload the config before adding the callback, so we can already use the config's values we defined above
372407
HyprlandAPI::reloadConfig();

0 commit comments

Comments
 (0)