diff --git a/plugins/wm-actions/wm-actions.cpp b/plugins/wm-actions/wm-actions.cpp index 5d7c2bc..61788b0 100644 --- a/plugins/wm-actions/wm-actions.cpp +++ b/plugins/wm-actions/wm-actions.cpp @@ -21,6 +21,8 @@ #include "wm-actions-signals.hpp" #include "wayfire/nonstd/reverse.hpp" +namespace +{ class always_on_top_root_node_t : public wf::scene::output_node_t { public: @@ -36,6 +38,45 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t { wf::scene::floating_inner_ptr always_above; bool showdesktop_active = false; + wf::wl_idle_call dialog_update; + + static bool restore_dialog_parent(wayfire_view view) + { + auto toplevel = wf::toplevel_cast(view); + if (!toplevel || !toplevel->parent) + { + return false; + } + + // Dialogs belong to their parent's scene tree. Repair dialogs detached + // by an older wm-actions instance during a live plugin upgrade too. + if (view->has_data("wm-actions-above") && + view->get_root_node()->parent() != toplevel->parent->get_root_node().get()) + { + wf::scene::readd_front(toplevel->parent->get_root_node(), view->get_root_node()); + } + + view->erase_data("wm-actions-above"); + view->erase_data("wm-actions-floating-above"); + view->erase_data("wm-actions-floating-manual"); + return true; + } + + void schedule_dialog_update() + { + // Wait for mapping/reparenting handlers before repairing dialog ownership. + dialog_update.run_once([this] + { + for (auto view : wf::get_core().get_all_views()) + { + if (view->get_output() != output || !view->is_mapped()) continue; + restore_dialog_parent(view); + } + }); + } + + wf::signal::connection_t dialog_mapped = + [this] (auto) { schedule_dialog_update(); }; wf::option_wrapper_t minimize{ "wm-actions/minimize"}; @@ -61,6 +102,8 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t bool set_keep_above_state(wayfire_view view, bool above) { + // A manual above request on a dialog applies to its whole window family. + view = wf::find_topmost_parent(view); if (!view || !output->can_activate_plugin(&grab_interface)) { return false; @@ -73,7 +116,10 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t "wm-actions-above"); } else { - wf::scene::readd_front(output->wset()->get_node(), view->get_root_node()); + // A tiler may already have moved the view into its own scene node. + // Clear the flag without taking that view out of its tiling tree. + if (view->get_root_node()->parent() == always_above.get()) + wf::scene::readd_front(output->wset()->get_node(), view->get_root_node()); if (view->has_data("wm-actions-above")) { view->erase_data("wm-actions-above"); @@ -122,7 +168,14 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t wf::signal::connection_t on_view_output_changed = [=] (wf::view_moved_to_wset_signal *signal) { - if (!signal->new_wset || (signal->new_wset->get_attached_output() != output)) + if (!signal->new_wset) + { + // A mapped independent window may have just become a dialog. + schedule_dialog_update(); + return; + } + + if (signal->new_wset->get_attached_output() != output) { return; } @@ -138,6 +191,7 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t { wf::scene::readd_front(always_above, view->get_root_node()); } + schedule_dialog_update(); }; /** @@ -152,10 +206,12 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t return; } - if (ev->view->has_data("wm-actions-above") && !ev->view->minimized) + if (!restore_dialog_parent(ev->view) && + ev->view->has_data("wm-actions-above") && !ev->view->minimized) { wf::scene::readd_front(always_above, ev->view->get_root_node()); } + schedule_dialog_update(); }; void check_disable_showdesktop(wayfire_view view) @@ -222,7 +278,7 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t */ wf::activator_callback on_toggle_above = [=] (auto ev) -> bool { - auto view = choose_view(ev.source); + auto view = wf::find_topmost_parent(choose_view(ev.source)); if (view) { return set_keep_above_state(view, !view->has_data("wm-actions-above")); @@ -384,17 +440,30 @@ class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t output->add_activator(bring_to_front, &on_bring_to_front); output->connect(&on_set_above_state_signal); output->connect(&on_view_minimized); + output->connect(&dialog_mapped); wf::get_core().connect(&on_view_output_changed); + schedule_dialog_update(); } void fini() override { + dialog_update.disconnect(); + for (auto view : wf::get_core().get_all_views()) + { + if (view->get_output() == output) + { + restore_dialog_parent(view); + } + } + for (auto view : output->wset()->get_views()) { if (view->has_data("wm-actions-above")) { set_keep_above_state(view, false); } + view->erase_data("wm-actions-floating-above"); + view->erase_data("wm-actions-floating-manual"); } wf::scene::remove_child(always_above); @@ -526,4 +595,5 @@ class wayfire_wm_actions_t : public wf::plugin_interface_t, }; }; +} DECLARE_WAYFIRE_PLUGIN(wayfire_wm_actions_t);