Skip to content

Commit d3c872b

Browse files
committed
xdg-activation: add focus stealing prevention when using tokens
1 parent 099fea4 commit d3c872b

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

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: 65 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,25 @@ 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 = wf::wl_surface_to_wayfire_view(token->surface->resource);
129+
if (view)
130+
{
131+
last_view = wf::toplevel_cast(view); // might return nullptr
132+
if (last_view)
133+
{
134+
last_view->connect(&on_view_unmapped);
135+
last_view->connect(&on_view_deactivated);
136+
}
137+
}
138+
103139
// update our token and connect its destroy signal
104140
last_token = token;
105141
xdg_activation_token_destroy.disconnect();
@@ -125,14 +161,43 @@ class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t
125161
}
126162
};
127163

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

134198
wf::option_wrapper_t<bool> check_surface{"xdg-activation/check_surface"};
135199
wf::option_wrapper_t<bool> only_last_token{"xdg-activation/only_last_request"};
200+
wf::option_wrapper_t<bool> prevent_focus_stealing{"xdg-activation/focus_stealing_prevention"};
136201
wf::option_wrapper_t<int> timeout{"xdg-activation/timeout"};
137202
};
138203

0 commit comments

Comments
 (0)