1 #include "output-impl.hpp"
2 #include "wayfire/view.hpp"
3 #include "../core/core-impl.hpp"
4 #include "wayfire/signal-definitions.hpp"
5 #include "wayfire/render-manager.hpp"
6 #include "wayfire/output-layout.hpp"
7 #include "wayfire/workspace-manager.hpp"
8 #include "wayfire/compositor-view.hpp"
9 #include "wayfire-shell.hpp"
10 #include "../core/seat/input-manager.hpp"
11 #include "../view/xdg-shell.hpp"
12 #include <wayfire/util/log.hpp>
13 #include <wayfire/nonstd/wlroots-full.hpp>
14
15 #include <algorithm>
16 #include <assert.h>
17
18 wf::output_t::output_t() = default;
19
output_impl_t(wlr_output * handle,const wf::dimensions_t & effective_size)20 wf::output_impl_t::output_impl_t(wlr_output *handle,
21 const wf::dimensions_t& effective_size)
22 {
23 this->bindings = std::make_unique<bindings_repository_t>(this);
24 this->set_effective_size(effective_size);
25 this->handle = handle;
26 workspace = std::make_unique<workspace_manager>(this);
27 render = std::make_unique<render_manager>(this);
28
29 view_disappeared_cb = [=] (wf::signal_data_t *data)
30 {
31 output_t::refocus(get_signaled_view(data));
32 };
33
34 connect_signal("view-disappeared", &view_disappeared_cb);
35 }
36
start_plugins()37 void wf::output_impl_t::start_plugins()
38 {
39 plugin = std::make_unique<plugin_manager>(this);
40 }
41
to_string() const42 std::string wf::output_t::to_string() const
43 {
44 return handle->name;
45 }
46
refocus(wayfire_view skip_view,uint32_t layers)47 void wf::output_impl_t::refocus(wayfire_view skip_view, uint32_t layers)
48 {
49 wayfire_view next_focus = nullptr;
50 auto views = workspace->get_views_on_workspace(
51 workspace->get_current_workspace(), layers);
52
53 const auto& suitable_for_focus = [&] (wayfire_view view)
54 {
55 return (view != skip_view) && view->is_mapped() &&
56 view->get_keyboard_focus_surface() && !view->minimized;
57 };
58
59 const auto& newer_than_candidate = [&] (wayfire_view view)
60 {
61 return (!next_focus ||
62 (next_focus->last_focus_timestamp < view->last_focus_timestamp));
63 };
64
65 for (auto toplevel : views)
66 {
67 for (auto& v : toplevel->enumerate_views())
68 {
69 if (suitable_for_focus(v) && newer_than_candidate(v))
70 {
71 next_focus = v;
72 }
73 }
74 }
75
76 focus_view(next_focus, 0u);
77 }
78
refocus(wayfire_view skip_view)79 void wf::output_t::refocus(wayfire_view skip_view)
80 {
81 uint32_t focused_layer = wf::get_core().get_focused_layer();
82 uint32_t layers = focused_layer <=
83 LAYER_WORKSPACE ? MIDDLE_LAYERS : focused_layer;
84
85 auto views = workspace->get_views_on_workspace(
86 workspace->get_current_workspace(), layers);
87
88 if (views.empty())
89 {
90 if (wf::get_core().get_active_output() == this)
91 {
92 LOGD("warning: no focused views in the focused layer, probably a bug");
93 }
94
95 /* Usually, we focus a layer so that a particular view has focus, i.e
96 * we expect that there is a view in the focused layer. However we
97 * should try to find reasonable focus in any focuseable layers if
98 * that is not the case, for ex. if there is a focused layer by a
99 * layer surface on another output */
100 layers = all_layers_not_below(focused_layer);
101 }
102
103 refocus(skip_view, layers);
104 }
105
~output_t()106 wf::output_t::~output_t()
107 {}
108
~output_impl_t()109 wf::output_impl_t::~output_impl_t()
110 {
111 // Release plugins before bindings
112 this->plugin.reset();
113 this->bindings.reset();
114 }
115
set_effective_size(const wf::dimensions_t & size)116 void wf::output_impl_t::set_effective_size(const wf::dimensions_t& size)
117 {
118 this->effective_size = size;
119 }
120
get_screen_size() const121 wf::dimensions_t wf::output_impl_t::get_screen_size() const
122 {
123 return this->effective_size;
124 }
125
get_relative_geometry() const126 wf::geometry_t wf::output_t::get_relative_geometry() const
127 {
128 auto size = get_screen_size();
129
130 return {
131 0, 0, size.width, size.height
132 };
133 }
134
get_layout_geometry() const135 wf::geometry_t wf::output_t::get_layout_geometry() const
136 {
137 auto box = wlr_output_layout_get_box(
138 wf::get_core().output_layout->get_handle(), handle);
139 if (box)
140 {
141 return *box;
142 } else
143 {
144 LOGE("Get layout geometry for an invalid output!");
145
146 return {0, 0, 1, 1};
147 }
148 }
149
ensure_pointer(bool center) const150 void wf::output_t::ensure_pointer(bool center) const
151 {
152 auto ptr = wf::get_core().get_cursor_position();
153 if (!center &&
154 (get_layout_geometry() & wf::point_t{(int)ptr.x, (int)ptr.y}))
155 {
156 return;
157 }
158
159 auto lg = get_layout_geometry();
160 wf::pointf_t target = {
161 lg.x + lg.width / 2.0,
162 lg.y + lg.height / 2.0,
163 };
164 wf::get_core().warp_cursor(target);
165 wf::get_core().set_cursor("default");
166 }
167
get_cursor_position() const168 wf::pointf_t wf::output_t::get_cursor_position() const
169 {
170 auto og = get_layout_geometry();
171 auto gc = wf::get_core().get_cursor_position();
172
173 return {gc.x - og.x, gc.y - og.y};
174 }
175
ensure_visible(wayfire_view v)176 bool wf::output_t::ensure_visible(wayfire_view v)
177 {
178 auto bbox = v->get_bounding_box();
179 auto g = this->get_relative_geometry();
180
181 /* Compute the percentage of the view which is visible */
182 auto intersection = wf::geometry_intersection(bbox, g);
183 double area = 1.0 * intersection.width * intersection.height;
184 area /= 1.0 * bbox.width * bbox.height;
185
186 if (area >= 0.1) /* View is somewhat visible, no need for anything special */
187 {
188 return false;
189 }
190
191 /* Otherwise, switch the workspace so the view gets maximum exposure */
192 int dx = bbox.x + bbox.width / 2;
193 int dy = bbox.y + bbox.height / 2;
194
195 int dvx = std::floor(1.0 * dx / g.width);
196 int dvy = std::floor(1.0 * dy / g.height);
197 auto cws = workspace->get_current_workspace();
198 workspace->request_workspace(cws + wf::point_t{dvx, dvy});
199
200 return true;
201 }
202
close_popups()203 void wf::output_impl_t::close_popups()
204 {
205 for (auto& v : workspace->get_views_in_layer(wf::ALL_LAYERS))
206 {
207 auto popup = dynamic_cast<wayfire_xdg_popup*>(v.get());
208 if (!popup || (popup->popup_parent == active_view.get()))
209 {
210 continue;
211 }
212
213 /* Ignore popups which have a popup as their parent. In those cases, we'll
214 * close the topmost popup and this will recursively destroy the others.
215 *
216 * Otherwise we get a race condition with wlroots. */
217 if (dynamic_cast<wayfire_xdg_popup*>(popup->popup_parent))
218 {
219 continue;
220 }
221
222 popup->close();
223 }
224 }
225
update_active_view(wayfire_view v,uint32_t flags)226 void wf::output_impl_t::update_active_view(wayfire_view v, uint32_t flags)
227 {
228 this->active_view = v;
229 if (this == wf::get_core().get_active_output())
230 {
231 wf::get_core().set_active_view(v);
232 }
233
234 if (flags & FOCUS_VIEW_CLOSE_POPUPS)
235 {
236 close_popups();
237 }
238 }
239
update_focus_timestamp(wayfire_view view)240 static void update_focus_timestamp(wayfire_view view)
241 {
242 if (view)
243 {
244 timespec ts;
245 clock_gettime(CLOCK_MONOTONIC, &ts);
246 view->last_focus_timestamp = ts.tv_sec * 1'000'000'000ll + ts.tv_nsec;
247 }
248 }
249
focus_view(wayfire_view v,uint32_t flags)250 void wf::output_impl_t::focus_view(wayfire_view v, uint32_t flags)
251 {
252 static wf::option_wrapper_t<bool>
253 all_dialogs_modal{"workarounds/all_dialogs_modal"};
254
255 const auto& make_view_visible = [this, flags] (wayfire_view view)
256 {
257 if (view->minimized)
258 {
259 view->minimize_request(false);
260 }
261
262 if (flags & FOCUS_VIEW_RAISE)
263 {
264 while (view->parent)
265 {
266 view = view->parent;
267 }
268
269 workspace->bring_to_front(view);
270 }
271 };
272
273 if (v && (workspace->get_view_layer(v) < wf::get_core().get_focused_layer()))
274 {
275 auto active_view = get_active_view();
276 if (active_view && (active_view->get_app_id().find("$unfocus") == 0))
277 {
278 /* This is the case where for ex. a panel has grabbed input focus,
279 * but user has clicked on another view so we want to dismiss the
280 * grab. We can't do that straight away because the client still
281 * holds the focus layer request.
282 *
283 * Instead, we want to deactive the $unfocus view, so that it can
284 * release the grab. At the same time, we bring the to-be-focused
285 * view on top, so that it gets the focus next. */
286 update_active_view(nullptr, flags);
287 make_view_visible(v);
288 update_focus_timestamp(v);
289 } else
290 {
291 LOGD("Denying focus request for a view from a lower layer than the"
292 " focused layer");
293 }
294
295 return;
296 }
297
298 focus_view_signal data;
299
300 if (!v || !v->is_mapped())
301 {
302 update_active_view(nullptr, flags);
303 data.view = nullptr;
304 emit_signal("focus-view", &data);
305 return;
306 }
307
308 while (all_dialogs_modal && v->parent && v->parent->is_mapped())
309 {
310 v = v->parent;
311 }
312
313 /* If no keyboard focus surface is set, then we don't want to focus the view */
314 if (v->get_keyboard_focus_surface() || interactive_view_from_view(v.get()))
315 {
316 make_view_visible(v);
317 update_focus_timestamp(v);
318 update_active_view(v, flags);
319 data.view = v;
320 emit_signal("view-focused", &data);
321 }
322 }
323
focus_view(wayfire_view v,bool raise)324 void wf::output_impl_t::focus_view(wayfire_view v, bool raise)
325 {
326 uint32_t flags = FOCUS_VIEW_CLOSE_POPUPS;
327 if (raise)
328 {
329 flags |= FOCUS_VIEW_RAISE;
330 }
331
332 focus_view(v, flags);
333 }
334
get_top_view() const335 wayfire_view wf::output_t::get_top_view() const
336 {
337 auto views = workspace->get_views_on_workspace(
338 workspace->get_current_workspace(),
339 LAYER_WORKSPACE);
340
341 return views.empty() ? nullptr : views[0];
342 }
343
get_active_view() const344 wayfire_view wf::output_impl_t::get_active_view() const
345 {
346 return active_view;
347 }
348
can_activate_plugin(uint32_t caps,uint32_t flags)349 bool wf::output_impl_t::can_activate_plugin(uint32_t caps,
350 uint32_t flags)
351 {
352 if (this->inhibited && !(flags & wf::PLUGIN_ACTIVATION_IGNORE_INHIBIT))
353 {
354 return false;
355 }
356
357 for (auto act_owner : active_plugins)
358 {
359 bool compatible = ((act_owner->capabilities & caps) == 0);
360 if (!compatible)
361 {
362 return false;
363 }
364 }
365
366 return true;
367 }
368
can_activate_plugin(const plugin_grab_interface_uptr & owner,uint32_t flags)369 bool wf::output_impl_t::can_activate_plugin(const plugin_grab_interface_uptr& owner,
370 uint32_t flags)
371 {
372 if (!owner)
373 {
374 return false;
375 }
376
377 if (active_plugins.find(owner.get()) != active_plugins.end())
378 {
379 return flags & wf::PLUGIN_ACTIVATE_ALLOW_MULTIPLE;
380 }
381
382 return can_activate_plugin(owner->capabilities, flags);
383 }
384
activate_plugin(const plugin_grab_interface_uptr & owner,uint32_t flags)385 bool wf::output_impl_t::activate_plugin(const plugin_grab_interface_uptr& owner,
386 uint32_t flags)
387 {
388 if (!can_activate_plugin(owner, flags))
389 {
390 return false;
391 }
392
393 if (active_plugins.find(owner.get()) != active_plugins.end())
394 {
395 LOGD("output ", handle->name,
396 ": activate plugin ", owner->name, " again");
397 } else
398 {
399 LOGD("output ", handle->name, ": activate plugin ", owner->name);
400 }
401
402 active_plugins.insert(owner.get());
403
404 return true;
405 }
406
deactivate_plugin(const plugin_grab_interface_uptr & owner)407 bool wf::output_impl_t::deactivate_plugin(
408 const plugin_grab_interface_uptr& owner)
409 {
410 auto it = active_plugins.find(owner.get());
411 if (it == active_plugins.end())
412 {
413 return true;
414 }
415
416 active_plugins.erase(it);
417 LOGD("output ", handle->name, ": deactivate plugin ", owner->name);
418
419 if (active_plugins.count(owner.get()) == 0)
420 {
421 owner->ungrab();
422 active_plugins.erase(owner.get());
423
424 return true;
425 }
426
427 return false;
428 }
429
cancel_active_plugins()430 void wf::output_impl_t::cancel_active_plugins()
431 {
432 std::vector<wf::plugin_grab_interface_t*> ifaces;
433 for (auto p : active_plugins)
434 {
435 if (p->callbacks.cancel)
436 {
437 ifaces.push_back(p);
438 }
439 }
440
441 for (auto p : ifaces)
442 {
443 p->callbacks.cancel();
444 }
445 }
446
is_plugin_active(std::string name) const447 bool wf::output_impl_t::is_plugin_active(std::string name) const
448 {
449 for (auto act : active_plugins)
450 {
451 if (act && (act->name == name))
452 {
453 return true;
454 }
455 }
456
457 return false;
458 }
459
get_input_grab_interface()460 wf::plugin_grab_interface_t*wf::output_impl_t::get_input_grab_interface()
461 {
462 for (auto p : active_plugins)
463 {
464 if (p && p->is_grabbed())
465 {
466 return p;
467 }
468 }
469
470 return nullptr;
471 }
472
inhibit_plugins()473 void wf::output_impl_t::inhibit_plugins()
474 {
475 this->inhibited = true;
476 cancel_active_plugins();
477 }
478
uninhibit_plugins()479 void wf::output_impl_t::uninhibit_plugins()
480 {
481 this->inhibited = false;
482 }
483
is_inhibited() const484 bool wf::output_impl_t::is_inhibited() const
485 {
486 return this->inhibited;
487 }
488
489 namespace wf
490 {
491 template<class Option, class Callback>
push_binding(binding_container_t<Option,Callback> & bindings,option_sptr_t<Option> opt,Callback * callback)492 static wf::binding_t *push_binding(
493 binding_container_t<Option, Callback>& bindings,
494 option_sptr_t<Option> opt,
495 Callback *callback)
496 {
497 auto bnd = std::make_unique<output_binding_t<Option, Callback>>();
498 bnd->activated_by = opt;
499 bnd->callback = callback;
500 bindings.emplace_back(std::move(bnd));
501
502 return bindings.back().get();
503 }
504
add_key(option_sptr_t<keybinding_t> key,wf::key_callback * callback)505 binding_t*output_impl_t::add_key(option_sptr_t<keybinding_t> key,
506 wf::key_callback *callback)
507 {
508 return push_binding(this->bindings->keys, key, callback);
509 }
510
add_axis(option_sptr_t<keybinding_t> axis,wf::axis_callback * callback)511 binding_t*output_impl_t::add_axis(option_sptr_t<keybinding_t> axis,
512 wf::axis_callback *callback)
513 {
514 return push_binding(this->bindings->axes, axis, callback);
515 }
516
add_button(option_sptr_t<buttonbinding_t> button,wf::button_callback * callback)517 binding_t*output_impl_t::add_button(option_sptr_t<buttonbinding_t> button,
518 wf::button_callback *callback)
519 {
520 return push_binding(this->bindings->buttons, button, callback);
521 }
522
add_activator(option_sptr_t<activatorbinding_t> activator,wf::activator_callback * callback)523 binding_t*output_impl_t::add_activator(
524 option_sptr_t<activatorbinding_t> activator, wf::activator_callback *callback)
525 {
526 auto result = push_binding(this->bindings->activators, activator, callback);
527 this->bindings->recreate_hotspots();
528 return result;
529 }
530
rem_binding(wf::binding_t * binding)531 void wf::output_impl_t::rem_binding(wf::binding_t *binding)
532 {
533 return this->bindings->rem_binding(binding);
534 }
535
rem_binding(void * callback)536 void wf::output_impl_t::rem_binding(void *callback)
537 {
538 return this->bindings->rem_binding(callback);
539 }
540
get_bindings()541 bindings_repository_t& output_impl_t::get_bindings()
542 {
543 return *bindings;
544 }
545
call_plugin(const std::string & activator,const wf::activator_data_t & data) const546 bool output_impl_t::call_plugin(
547 const std::string& activator, const wf::activator_data_t& data) const
548 {
549 return this->bindings->handle_activator(activator, data);
550 }
551
all_layers_not_below(uint32_t layer)552 uint32_t all_layers_not_below(uint32_t layer)
553 {
554 uint32_t mask = 0;
555 for (int i = 0; i < wf::TOTAL_LAYERS; i++)
556 {
557 if ((1u << i) >= layer)
558 {
559 mask |= (1 << i);
560 }
561 }
562
563 return mask;
564 }
565 } // namespace wf
566