diff --git a/metadata/meson.build b/metadata/meson.build index 16f89631d..64c23b368 100644 --- a/metadata/meson.build +++ b/metadata/meson.build @@ -8,6 +8,7 @@ install_data('cube.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('decoration.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('expo.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('extra-gestures.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) +install_data('ext-toplevel.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('fast-switcher.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('fisheye.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) install_data('foreign-toplevel.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')) diff --git a/plugins/protocols/ext-toplevel.cpp b/plugins/protocols/ext-toplevel.cpp index a7b28d5e5..ae9366ca6 100644 --- a/plugins/protocols/ext-toplevel.cpp +++ b/plugins/protocols/ext-toplevel.cpp @@ -10,12 +10,13 @@ #include #include #include "gtk-shell.hpp" +#include "../wm-actions/wm-actions-signals.hpp" #include "config.h" #include "toplevel-common.hpp" -class wayfire_foreign_toplevel; -using foreign_toplevel_map_type = std::map>; +class wayfire_ext_foreign_toplevel; +using foreign_toplevel_map_type = std::map>; void get_state(wayfire_view view, struct wlr_ext_foreign_toplevel_handle_v1_state *state) { @@ -31,11 +32,16 @@ class wayfire_ext_foreign_toplevel { wayfire_toplevel_view view; wlr_ext_foreign_toplevel_handle_v1 *handle; + wlr_ext_foreign_toplevel_state_handle_v1 *state_handle; + foreign_toplevel_map_type *view_to_toplevel; public: - wayfire_ext_foreign_toplevel(wayfire_toplevel_view view, wlr_ext_foreign_toplevel_handle_v1 *hndl) : + wayfire_ext_foreign_toplevel(wayfire_toplevel_view view, wlr_ext_foreign_toplevel_handle_v1 *hndl, + wlr_ext_foreign_toplevel_state_handle_v1 *st, foreign_toplevel_map_type *view_map) : view(view), - handle(hndl) + handle(hndl), + state_handle(st), + view_to_toplevel(view_map) { /** * This is future-proofing. @@ -65,35 +71,122 @@ class wayfire_ext_foreign_toplevel protected: virtual void init_request_handlers() { - // No request handlers at the present moment. - // We may want to deal with them later on while implementing - // ext-foreign-toplevel-management protocol. + toplevel_handle_v1_maximize_request.set_callback([&] (void *data) + { + auto ev = static_cast(data); + wf::get_core().default_wm->tile_request(view, ev->maximized ? wf::TILED_EDGES_ALL : 0); + }); + + toplevel_handle_v1_minimize_request.set_callback([&] (void *data) + { + auto ev = static_cast(data); + wf::get_core().default_wm->minimize_request(view, ev->minimized); + }); + + toplevel_handle_v1_activate_request.set_callback([&] (auto) + { + wf::get_core().default_wm->focus_request(view); + }); + + toplevel_handle_v1_close_request.set_callback([&] (auto) + { + view->close(); + }); + + toplevel_handle_v1_set_rectangle_request.set_callback([&] (void *data) + { + auto ev = static_cast(data); + auto surface = wf::wl_surface_to_wayfire_view(ev->surface->resource); + if (!surface) + { + LOGE("Setting minimize hint to unknown surface. Wayfire currently" + "supports only setting hints relative to views."); + return; + } + + handle_minimize_hint(view.get(), surface.get(), {ev->x, ev->y, ev->width, ev->height}); + }); + + toplevel_handle_v1_fullscreen_request.set_callback([&] ( + void *data) + { + auto ev = static_cast(data); + auto wo = wf::get_core().output_layout->find_output(ev->output); + wf::get_core().default_wm->fullscreen_request(view, wo, ev->fullscreen); + }); + + toplevel_handle_v1_above_request.set_callback([&] (void *data) + { + auto ev = static_cast(data); + wf::wm_actions_set_above_state_signal sig; + + auto output = view->get_output(); + if (!output) + { + return; + } + + sig.view = view; + sig.above = ev->always_on_top; + output->emit(&sig); + }); + + toplevel_handle_v1_sticky_request.set_callback([&] (void *data) + { + auto ev = static_cast(data); + view->set_sticky(ev->sticky); + }); + + toplevel_handle_v1_close_request.connect(&state_handle->events.request_close); + toplevel_handle_v1_maximize_request.connect(&state_handle->events.request_maximize); + toplevel_handle_v1_minimize_request.connect(&state_handle->events.request_minimize); + toplevel_handle_v1_activate_request.connect(&state_handle->events.request_activate); + toplevel_handle_v1_fullscreen_request.connect(&state_handle->events.request_fullscreen); + toplevel_handle_v1_set_rectangle_request.connect(&state_handle->events.set_rectangle); + toplevel_handle_v1_above_request.connect(&state_handle->events.request_always_on_top); + toplevel_handle_v1_sticky_request.connect(&state_handle->events.request_sticky); } virtual void send_initial_state() { - toplevel_send_state(); + toplevel_send_title_appid(); + toplevel_update_output(view->get_output(), true); + toplevel_send_additional_state(); } virtual void init_connections() { view->connect(&on_title_changed); view->connect(&on_app_id_changed); + view->connect(&on_set_output); + view->connect(&on_tiled); + view->connect(&on_minimized); + view->connect(&on_fullscreen); + view->connect(&on_activated); + view->connect(&on_parent_changed); + view->connect(&on_above_changed); + view->connect(&on_sticky_changed); } virtual void disconnect_request_handlers() { - // No request handlers at the present moment. - // We may want to deal with them later on while implementing - // ext-foreign-toplevel-management protocol. + toplevel_handle_v1_close_request.disconnect(); + toplevel_handle_v1_maximize_request.disconnect(); + toplevel_handle_v1_minimize_request.disconnect(); + toplevel_handle_v1_activate_request.disconnect(); + toplevel_handle_v1_fullscreen_request.disconnect(); + toplevel_handle_v1_set_rectangle_request.disconnect(); + toplevel_handle_v1_above_request.disconnect(); + toplevel_handle_v1_sticky_request.disconnect(); } virtual void destroy_handle() { + wlr_ext_foreign_toplevel_state_handle_v1_destroy(state_handle); wlr_ext_foreign_toplevel_handle_v1_destroy(handle); } - virtual void toplevel_send_state() + virtual void toplevel_send_title_appid() { // Prepare the state struct wlr_ext_foreign_toplevel_handle_v1_state new_state; @@ -103,16 +196,116 @@ class wayfire_ext_foreign_toplevel wlr_ext_foreign_toplevel_handle_v1_update_state(handle, &new_state); } + + virtual void toplevel_send_additional_state() + { + /** Additional state (note: these are no-ops if there is not change from the current state */ + wlr_ext_foreign_toplevel_state_handle_v1_set_maximized(state_handle, view->pending_tiled_edges() == wf::TILED_EDGES_ALL); + wlr_ext_foreign_toplevel_state_handle_v1_set_activated(state_handle, view->activated); + wlr_ext_foreign_toplevel_state_handle_v1_set_minimized(state_handle, view->minimized); + wlr_ext_foreign_toplevel_state_handle_v1_set_fullscreen(state_handle, view->pending_fullscreen()); + bool is_above = view->has_data("wm-actions-above"); //!! TODO: is there a better way to get this? + wlr_ext_foreign_toplevel_state_handle_v1_set_always_on_top(state_handle, is_above); + wlr_ext_foreign_toplevel_state_handle_v1_set_sticky(state_handle, view->sticky); + + /* update parent as well */ + auto it = view_to_toplevel->find(view->parent); + if (it == view_to_toplevel->end()) + { + wlr_ext_foreign_toplevel_state_handle_v1_set_parent(state_handle, nullptr); + } else + { + wlr_ext_foreign_toplevel_state_handle_v1_set_parent(state_handle, it->second->state_handle); + } + } + + void toplevel_update_output(wf::output_t *output, bool enter) + { + if (output && enter) + { + wlr_ext_foreign_toplevel_state_handle_v1_output_enter(state_handle, output->handle); + } + + if (output && !enter) + { + wlr_ext_foreign_toplevel_state_handle_v1_output_leave(state_handle, output->handle); + } + } wf::signal::connection_t on_title_changed = [=] (auto) { - toplevel_send_state(); + toplevel_send_title_appid(); }; wf::signal::connection_t on_app_id_changed = [=] (auto) { - toplevel_send_state(); + toplevel_send_title_appid(); }; + + wf::signal::connection_t on_set_output = [=] (wf::view_set_output_signal *ev) + { + toplevel_update_output(ev->output, false); + toplevel_update_output(view->get_output(), true); + }; + + wf::signal::connection_t on_minimized = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_fullscreen = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_tiled = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_activated = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_parent_changed = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_above_changed = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::signal::connection_t on_sticky_changed = [=] (auto) + { + toplevel_send_additional_state(); + }; + + wf::wl_listener_wrapper toplevel_handle_v1_maximize_request; + wf::wl_listener_wrapper toplevel_handle_v1_activate_request; + wf::wl_listener_wrapper toplevel_handle_v1_minimize_request; + wf::wl_listener_wrapper toplevel_handle_v1_set_rectangle_request; + wf::wl_listener_wrapper toplevel_handle_v1_fullscreen_request; + wf::wl_listener_wrapper toplevel_handle_v1_close_request; + wf::wl_listener_wrapper toplevel_handle_v1_above_request; + wf::wl_listener_wrapper toplevel_handle_v1_sticky_request; + + void handle_minimize_hint(wf::toplevel_view_interface_t *view, wf::view_interface_t *relative_to, + wlr_box hint) + { + if (relative_to->get_output() != view->get_output()) + { + LOGE("Minimize hint set to surface on a different output, " "problems might arise"); + /* TODO: translate coordinates in case minimize hint is on another output */ + } + + wf::pointf_t relative = relative_to->get_surface_root_node()->to_global({0, 0}); + hint.x += relative.x; + hint.y += relative.y; + view->set_minimize_hint(hint); + } }; class wayfire_ext_foreign_toplevel_protocol_impl : public wf::plugin_interface_t @@ -127,6 +320,17 @@ class wayfire_ext_foreign_toplevel_protocol_impl : public wf::plugin_interface_t return; } + state_manager = wlr_ext_foreign_toplevel_state_manager_v1_create(wf::get_core().display, + 63, // possible notifications: maximize, minimize, activated, fullscreen, always_on_top, sticky -- TODO: use #defines from protocol header !! + 127 // possible actions: close, maximize, minimize, activate, fullscreen, always_on_top, sticky + ); + if (!state_manager) + { + LOGE("Failed to create foreign toplevel state manager"); + //!! TODO: is there no way to destroy toplevel_manager ?? + return; + } + wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_view_unmapped); } @@ -159,7 +363,15 @@ class wayfire_ext_foreign_toplevel_protocol_impl : public wf::plugin_interface_t return; } - handle_for_view[toplevel] = std::make_unique(toplevel, handle); + auto state = wlr_ext_foreign_toplevel_state_handle_v1_create (state_manager, handle); + if (!state) + { + LOGE("Failed to create foreign toplevel state handle for view"); + wlr_ext_foreign_toplevel_handle_v1_destroy (handle); + return; + } + + handle_for_view[toplevel] = std::make_unique(toplevel, handle, state, &handle_for_view); } }; @@ -169,6 +381,7 @@ class wayfire_ext_foreign_toplevel_protocol_impl : public wf::plugin_interface_t }; wlr_ext_foreign_toplevel_list_v1 *toplevel_manager; + wlr_ext_foreign_toplevel_state_manager_v1 *state_manager; std::map> handle_for_view; }; diff --git a/src/api/wayfire/nonstd/wlroots-full.hpp b/src/api/wayfire/nonstd/wlroots-full.hpp index 719970eb3..5ef278b78 100644 --- a/src/api/wayfire/nonstd/wlroots-full.hpp +++ b/src/api/wayfire/nonstd/wlroots-full.hpp @@ -56,6 +56,7 @@ extern "C" #include #include #include +#include #if __has_include() #include #include