Skip to content

Commit 59008c2

Browse files
committed
xdg-activation: add focus stealing prevention when using tokens
1 parent f08a9aa commit 59008c2

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

metadata/xdg-activation.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
<_long>Whether to reject activation requests if a newer request has arrived since their creation.</_long>
1515
<default>false</default>
1616
</option>
17+
<option name="focus_stealing_prevention" type="bool">
18+
<_short>Prevent stealing focus by an activation request</_short>
19+
<_long>Whether to reject an activation request if the user interacted with a different view since its creation.</_long>
20+
<default>true</default>
21+
</option>
1722
<option name="timeout" type="int">
1823
<_short>Timeout for activation (in seconds)</_short>
1924
<_long>Focus requests will be ignored if at least this amount of time has elapsed between creating and using it.</_long>

plugins/protocols/xdg-activation.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
3636
xdg_activation_new_token.disconnect();
3737
xdg_activation_token_destroy.disconnect();
3838
last_token = nullptr;
39+
if (last_view)
40+
{
41+
last_view->disconnect(&on_view_unmapped);
42+
last_view->disconnect(&on_view_deactivated);
43+
last_view = nullptr;
44+
}
3945
}
4046

4147
bool is_unloadable() override
@@ -64,6 +70,17 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
6470

6571
last_token = nullptr; // avoid reusing the same token
6672

73+
if (last_view)
74+
{
75+
last_view->disconnect(&on_view_unmapped);
76+
last_view->disconnect(&on_view_deactivated);
77+
last_view = nullptr;
78+
} else if (prevent_focus_stealing)
79+
{
80+
LOGI("Denying focus request, requesting view has been deactivated");
81+
return;
82+
}
83+
6784
wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource);
6885
if (!view)
6986
{
@@ -100,6 +117,26 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
100117
return;
101118
}
102119

120+
// unset any previously saved view
121+
if (last_view)
122+
{
123+
last_view->disconnect(&on_view_unmapped);
124+
last_view->disconnect(&on_view_deactivated);
125+
last_view = nullptr;
126+
}
127+
128+
wayfire_view view = token->surface ? wf::wl_surface_to_wayfire_view(
129+
token->surface->resource) : nullptr;
130+
if (view)
131+
{
132+
last_view = wf::toplevel_cast(view); // might return nullptr
133+
if (last_view)
134+
{
135+
last_view->connect(&on_view_unmapped);
136+
last_view->connect(&on_view_deactivated);
137+
}
138+
}
139+
103140
// update our token and connect its destroy signal
104141
last_token = token;
105142
xdg_activation_token_destroy.disconnect();
@@ -125,14 +162,43 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
125162
}
126163
};
127164

165+
wf::signal::connection_t<wf::view_unmapped_signal> on_view_unmapped = [this] (auto)
166+
{
167+
last_view->disconnect(&on_view_unmapped);
168+
last_view->disconnect(&on_view_deactivated);
169+
// handle the case when last_view was a dialog that is closed by user interaction
170+
last_view = last_view->parent;
171+
if (last_view)
172+
{
173+
last_view->connect(&on_view_unmapped);
174+
last_view->connect(&on_view_deactivated);
175+
}
176+
};
177+
178+
wf::signal::connection_t<wf::view_activated_state_signal> on_view_deactivated = [this] (auto)
179+
{
180+
if (last_view->activated)
181+
{
182+
// could be a spurious event, e.g. activating the parent
183+
// view after closing a dialog
184+
return;
185+
}
186+
187+
last_view->disconnect(&on_view_unmapped);
188+
last_view->disconnect(&on_view_deactivated);
189+
last_view = nullptr;
190+
};
191+
128192
struct wlr_xdg_activation_v1 *xdg_activation;
129193
wf::wl_listener_wrapper xdg_activation_request_activate;
130194
wf::wl_listener_wrapper xdg_activation_new_token;
131195
wf::wl_listener_wrapper xdg_activation_token_destroy;
132196
struct wlr_xdg_activation_token_v1 *last_token = nullptr;
197+
wayfire_toplevel_view last_view = nullptr; // view that created the token
133198

134199
wf::option_wrapper_t<bool> check_surface{"xdg-activation/check_surface"};
135200
wf::option_wrapper_t<bool> only_last_token{"xdg-activation/only_last_request"};
201+
wf::option_wrapper_t<bool> prevent_focus_stealing{"xdg-activation/focus_stealing_prevention"};
136202
wf::option_wrapper_t<int> timeout{"xdg-activation/timeout"};
137203
};
138204

0 commit comments

Comments
 (0)