diff --git a/src/force-fullscreen.cpp b/src/force-fullscreen.cpp index 0a4e1a1..0d3b07c 100644 --- a/src/force-fullscreen.cpp +++ b/src/force-fullscreen.cpp @@ -23,6 +23,7 @@ * SOFTWARE. */ +#include #include #include #include @@ -359,6 +360,30 @@ class wayfire_force_fullscreen : public wf::per_output_plugin_instance_t return true; } + /* Continue a force-fullscreen handed over from another output, keeping + * the windowed size and position instead of the transient move geometry. */ + void adopt_fullscreen(wayfire_toplevel_view view, wf::geometry_t windowed) + { + if (!toggle_fullscreen(view)) + { + return; + } + + auto background = backgrounds.find(view); + if (background == backgrounds.end()) + { + return; + } + + auto og = output->get_relative_geometry(); + using coord_t = decltype(windowed.x); + windowed.x = std::clamp(windowed.x, 0, std::max(0, og.width - windowed.width)); + windowed.y = std::clamp(windowed.y, 0, std::max(0, og.height - windowed.height)); + background->second->undecorated_geometry = windowed; + view->set_geometry(windowed); + setup_transform(view); + } + wf::key_callback on_toggle_fullscreen = [=] (auto) { auto view = wf::toplevel_cast(wf::get_active_view_for_output(output)); @@ -551,6 +576,7 @@ class wayfire_force_fullscreen : public wf::per_output_plugin_instance_t } auto target = ev->new_wset ? ev->new_wset->get_attached_output() : nullptr; + auto windowed = background->second->undecorated_geometry; // The move has completed, so scope resolution sees the new output. view->toplevel()->pending().fullscreen = false; @@ -560,7 +586,7 @@ class wayfire_force_fullscreen : public wf::per_output_plugin_instance_t auto instance = wayfire_force_fullscreen_instances.find(target); if (instance != wayfire_force_fullscreen_instances.end()) { - instance->second->toggle_fullscreen(view); + instance->second->adopt_fullscreen(view, windowed); } } }; diff --git a/src/join-views.cpp b/src/join-views.cpp index 7c565cb..c15c417 100644 --- a/src/join-views.cpp +++ b/src/join-views.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -65,10 +67,29 @@ class JoinViewsSingleton output->connect(&on_view_unmap); } + wf::option_sptr_t join_option; + bool previous_join = false; + + // Move must drag joined children while this plugin is loaded. Lock the + // option so a config reload cannot undo it, and restore it on unload. JoinViewsSingleton() { - wf::option_wrapper_t opt{"move/join_views"}; - ((wf::option_sptr_t)opt)->set_value(true); + join_option = wf::get_core().config->get_option("move/join_views"); + if (join_option) + { + previous_join = join_option->get_value(); + join_option->set_locked(true); + join_option->set_value(true); + } + } + + ~JoinViewsSingleton() + { + if (join_option) + { + join_option->set_locked(false); + join_option->set_value(previous_join); + } } }; diff --git a/src/obs.cpp b/src/obs.cpp index 4499329..0c05021 100644 --- a/src/obs.cpp +++ b/src/obs.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -104,7 +105,24 @@ class wf_obs : public wf::scene::view_2d_transformer_t std::unique_ptr opacity; std::unique_ptr brightness; std::unique_ptr saturation; - bool hook_set = false; + // The output whose render loop runs pre_hook; it follows the view. + wf::output_t *hook_output = nullptr; + wf::signal::connection_t on_output_removed = + [=] (wf::output_pre_remove_signal *ev) + { + if (ev->output == hook_output) + { + hook_output->render->rem_effect(&pre_hook); + hook_output = nullptr; + } + }; + wf::signal::connection_t on_view_output = [=] (wf::view_set_output_signal*) + { + if (hook_output || progression_running() || !transformer_inert()) + { + set_hook(); + } + }; public: class simple_node_render_instance_t : public wf::scene::transformer_render_instance_t @@ -216,6 +234,8 @@ class wf_obs : public wf::scene::view_2d_transformer_t opacity->set(1.0, 1.0); brightness->set(1.0, 1.0); saturation->set(1.0, 1.0); + wf::get_core().output_layout->connect(&on_output_removed); + view->connect(&on_view_output); } void gen_render_instances(std::vector& instances, @@ -227,28 +247,41 @@ class wf_obs : public wf::scene::view_2d_transformer_t wf::effect_hook_t pre_hook = [=] () { + if (view->get_output() != hook_output) + { + set_hook(); + return; + } + if (this->progression_running()) { view->damage(); } else if (this->transformer_inert() && view->get_transformed_node()->get_transformer(transformer_name)) { - view->get_output()->render->rem_effect(&pre_hook); + hook_output->render->rem_effect(&pre_hook); + hook_output = nullptr; view->get_transformed_node()->rem_transformer(transformer_name); } }; void set_hook() { - if (this->hook_set) + auto output = view->get_output(); + if (output == hook_output) { return; } - if (auto output = view->get_output()) + if (hook_output) + { + hook_output->render->rem_effect(&pre_hook); + } + + hook_output = output; + if (hook_output) { - output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); - this->hook_set = true; + hook_output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); } } @@ -333,9 +366,9 @@ class wf_obs : public wf::scene::view_2d_transformer_t brightness.reset(); saturation.reset(); - for (auto & output : wf::get_core().output_layout->get_outputs()) + if (hook_output) { - output->render->rem_effect(&pre_hook); + hook_output->render->rem_effect(&pre_hook); } } }; diff --git a/src/pin-view.cpp b/src/pin-view.cpp index b76752f..6aa60d6 100644 --- a/src/pin-view.cpp +++ b/src/pin-view.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -61,8 +62,15 @@ class wayfire_pin_view : public wf::plugin_interface_t { output->connect(&on_workspace_changed); } + + wf::get_core().output_layout->connect(&on_output_added); } + wf::signal::connection_t on_output_added = [=] (wf::output_added_signal *ev) + { + ev->output->connect(&on_workspace_changed); + }; + wf::ipc::method_callback ipc_pin_view = [=] (wf::json_t data) -> wf::json_t { auto view_id = wf::ipc::json_get_uint64(data, "view-id"); @@ -75,8 +83,19 @@ class wayfire_pin_view : public wf::plugin_interface_t auto view = wf::ipc::find_view_by_id(view_id); if (view) { - bool was_pinned = unpin(view); - auto output = view->get_output(); + auto output = view->get_output(); + if (!output) + { + return wf::ipc::json_error("View has no output."); + } + + // Pinned views must stay below the session lock. + if (layer_str == "lock") + { + return wf::ipc::json_error("Views cannot be pinned to the lock layer."); + } + + bool was_pinned = restore(view); if (!view->get_data()) { pin_view_data pv_data; @@ -113,9 +132,6 @@ class wayfire_pin_view : public wf::plugin_interface_t } else if (layer_str == "overlay") { layer = wf::scene::layer::OVERLAY; - } else if (layer_str == "lock") - { - layer = wf::scene::layer::LOCK; } else { layer = wf::scene::layer::TOP; @@ -124,9 +140,14 @@ class wayfire_pin_view : public wf::plugin_interface_t auto pvd = view->get_data(); auto og = output->get_relative_geometry(); int x = 0, y = 0; - wf::view_unmapped_signal unmap_signal; - unmap_signal.view = view; - wf::get_core().emit(&unmap_signal); + // Hide a newly pinned view from window lists; a re-pin keeps it hidden. + if (!was_pinned && view->is_mapped()) + { + wf::view_unmapped_signal unmap_signal; + unmap_signal.view = view; + wf::get_core().emit(&unmap_signal); + } + if (optional_x.has_value()) { x = optional_x.value(); @@ -182,27 +203,42 @@ class wayfire_pin_view : public wf::plugin_interface_t return wf::ipc::json_ok(); }; + /* Return a pinned view to its workspace set without announcing it. */ + bool restore(wayfire_view view) + { + auto pvd = view ? view->get_data() : nullptr; + auto output = view ? view->get_output() : nullptr; + if (!pvd || !output) + { + return false; + } + + view->role = pvd->role; + wf::scene::readd_front(output->wset()->get_node(), view->get_root_node()); + if (auto toplevel = toplevel_cast(view)) + { + output->wset()->add_view(toplevel); + toplevel->set_geometry(pvd->geometry); + } + + return true; + } + bool unpin(wayfire_view view) { - if (view && view->get_data()) + if (!restore(view)) { - auto pvd = view->get_data(); - view->role = pvd->role; - wf::scene::readd_front(view->get_output()->wset()->get_node(), view->get_root_node()); - if (auto toplevel = toplevel_cast(view)) - { - auto output = view->get_output(); - output->wset()->add_view(toplevel); - toplevel->set_geometry(pvd->geometry); - } + return false; + } + if (view->is_mapped()) + { wf::view_mapped_signal map_signal; map_signal.view = view; wf::get_core().emit(&map_signal); - return true; } - return false; + return true; } wf::ipc::method_callback ipc_unpin_view = [=] (wf::json_t data) -> wf::json_t @@ -231,7 +267,7 @@ class wayfire_pin_view : public wf::plugin_interface_t for (auto & view : wf::get_core().get_all_views()) { auto pvd = view->get_data(); - if (!pvd) + if (!pvd || (view->get_output() != output)) { continue; } @@ -265,6 +301,7 @@ class wayfire_pin_view : public wf::plugin_interface_t ipc_repo->unregister_method("pin-view/pin"); ipc_repo->unregister_method("pin-view/unpin"); on_workspace_changed.disconnect(); + on_output_added.disconnect(); } }; } diff --git a/src/view-shot.cpp b/src/view-shot.cpp index f443309..c168621 100644 --- a/src/view-shot.cpp +++ b/src/view-shot.cpp @@ -22,6 +22,7 @@ * SOFTWARE. */ +#include #include #include #include @@ -77,10 +78,15 @@ class wayfire_view_shot : public wf::plugin_interface_t return false; } - char _file_name[255]; + char _file_name[PATH_MAX]; auto time = std::time(nullptr); - std::strftime(_file_name, sizeof(_file_name), - file_name.value().c_str(), std::localtime(&time)); + if (std::strftime(_file_name, sizeof(_file_name), + file_name.value().c_str(), std::localtime(&time)) == 0) + { + LOGE("view-shot: filename pattern is empty or too long"); + return false; + } + std::string formatted_file_name = _file_name; if (take_snapshot(view, formatted_file_name))