From 6c7cf55bd4d25555da00c0109925afbc7d376d6b Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Thu, 5 Oct 2023 23:18:32 +0200 Subject: [PATCH] background-view: rewrite plugin to use newer Wayfire helpers and custom views --- meson.build | 2 + proto/meson.build | 30 ++-- src/background-view.cpp | 330 ++++++++++++++++++++++++++-------------- src/meson.build | 2 +- 4 files changed, 233 insertions(+), 131 deletions(-) diff --git a/meson.build b/meson.build index cd69159..79d3eef 100644 --- a/meson.build +++ b/meson.build @@ -15,6 +15,8 @@ project( wayfire = dependency('wayfire') giomm = dependency('giomm-2.4', required: false) +wayland_protos = dependency('wayland-protocols', version: '>=1.12') +wayland_server = dependency('wayland-server') if get_option('enable_windecor') == true windecor = subproject('windecor') diff --git a/proto/meson.build b/proto/meson.build index 996fc8a..a306d25 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -1,9 +1,6 @@ -wayland_protos = dependency('wayland-protocols') -wayland_client = dependency('wayland-client') +wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir') -wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') - -wayland_scanner = find_program('wayland-scanner') +wayland_scanner = find_program('wayland-scanner', native: true) wayland_scanner_server = generator( wayland_scanner, @@ -17,28 +14,21 @@ wayland_scanner_code = generator( arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) -wayland_scanner_client = generator( - wayland_scanner, - output: '@BASENAME@-client-protocol.h', - arguments: ['client-header', '@INPUT@', '@OUTPUT@'], -) - -client_protocols = [ - './virtual-keyboard-unstable-v1.xml', - './wlr-input-inhibitor-unstable-v1.xml' +server_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], ] -wl_protos_client_src = [] +wl_protos_src = [] wl_protos_headers = [] -foreach p : client_protocols +foreach p : server_protocols xml = join_paths(p) - wl_protos_headers += wayland_scanner_client.process(xml) - wl_protos_client_src += wayland_scanner_code.process(xml) + wl_protos_src += wayland_scanner_code.process(xml) + wl_protos_headers += wayland_scanner_server.process(xml) endforeach -lib_wl_protos = static_library('wl_protos', wl_protos_client_src + wl_protos_headers, - dependencies: [wayland_client]) # for the include directory +lib_wl_protos = static_library('wl_protos', wl_protos_src + wl_protos_headers, + dependencies: [wayland_server]) # for the include directory wf_protos = declare_dependency( link_with: lib_wl_protos, diff --git a/src/background-view.cpp b/src/background-view.cpp index 81f8e6e..9576cfd 100644 --- a/src/background-view.cpp +++ b/src/background-view.cpp @@ -23,10 +23,17 @@ */ #include +#include +#include #include +#include +#include #include #include +#include +#include #include +#include #include #include #include @@ -34,34 +41,169 @@ #include #include #include -#include - +#include +#include +#include +#include +#include +#include #include -#if WF_HAS_XWAYLAND -extern "C" + +class wayfire_bgview_set_pointer_interaction : public wf::pointer_interaction_t { - #define class class_t - #define static - #include - #undef static - #undef class -} -#endif + public: + void handle_pointer_enter(wf::pointf_t position) override + { + wf::get_core().set_cursor("default"); + } +}; + +// A main node for the view. It allows or denies keyboard and pointer focus according to inhibit_output. +class wayfire_background_view_root_node : public wf::scene::translation_node_t +{ + std::weak_ptr _view; + wf::option_wrapper_t inhibit_input{"background-view/inhibit_input"}; + std::unique_ptr wlr_kb_interaction; + + public: + wayfire_background_view_root_node(wf::view_interface_t * _view) : translation_node_t(false) + { + this->_view = _view->weak_from_this(); + this->wlr_kb_interaction = std::make_unique(_view); + } + + std::optional find_node_at(const wf::pointf_t& point) override + { + if (inhibit_input) + { + // Prohibit pointer/touch input, but need to set the pointer on enter => we grab the input to this + // node in those cases, and the pointer_interaction.enter() sets the cursor to default + return wf::scene::input_node_t{ + .node = {this}, + .local_coords = point, + }; + } + + // Propagate event to the actual view node + return floating_inner_node_t::find_node_at(point); + } + + wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override + { + auto view = _view.lock(); + if (inhibit_input || !view) + { + // Prohibit keyboard input + return wf::keyboard_focus_node_t{}; + } + + // Take input only if the user explicitly clicks on the view => in this case, the last focus timestamp + // on the seat will be equal to our focus timestamp. + if (output != view->get_output()) + { + return wf::keyboard_focus_node_t{}; + } + + const uint64_t last_ts = wf::get_core().seat->get_last_focus_timestamp(); + const uint64_t our_ts = keyboard_interaction().last_focus_timestamp; + if (our_ts == last_ts) + { + return wf::keyboard_focus_node_t{this, wf::focus_importance::REGULAR}; + } + + return wf::keyboard_focus_node_t{}; + } + + wf::pointer_interaction_t& pointer_interaction() override + { + static wayfire_bgview_set_pointer_interaction itr; + return itr; + } + + wf::keyboard_interaction_t& keyboard_interaction() override + { + return *wlr_kb_interaction; + } + + std::string stringify() const override + { + return "background-view node " + stringify_flags(); + } +}; -struct parent_view +// The view type which background-views use. +// It derives from wf::view_interface_t directly instead of deriving from wf::toplevel_view_interface_t. +// This makes it so that the view appears similarly to a layer-shell view in the eyes of other plugins. +class unmappable_view_t : public virtual wf::view_interface_t { - nonstd::observer_ptr view; + public: + virtual void bg_view_unmap() = 0; + + wf::wl_listener_wrapper on_unmap; + std::shared_ptr root_node; + + template + static std::shared_ptr create( + WlrObject *toplevel, wf::output_t *output) + { + auto new_view = wf::view_interface_t::create(toplevel); + + new_view->role = wf::VIEW_ROLE_DESKTOP_ENVIRONMENT; + new_view->root_node = std::make_shared(new_view.get()); + + // Pin the view to (0, 0) on its output: backgrounds always span the whole output + new_view->root_node->set_offset({0, 0}); + new_view->set_surface_root_node(new_view->root_node); + + new_view->set_output(output); + wf::scene::add_front(output->node_for_layer(wf::scene::layer::BACKGROUND), new_view->get_root_node()); + + // Directly map the view, we know that the old_view comes from a pre_map signal, so it is already + // mapped on the wlroots side + new_view->map(); + wf::view_implementation::emit_view_map_signal({new_view.get()}, true); + return new_view; + } }; -static std::map views; +// An adapter so that xdg-shell views can be used as background views +class wayfire_background_view_xdg : public wf::xdg_toplevel_view_base_t, public unmappable_view_t +{ + public: + wayfire_background_view_xdg(wlr_xdg_toplevel *toplevel) : xdg_toplevel_view_base_t(toplevel, true) + {} + + void bg_view_unmap() override + { + wf::xdg_toplevel_view_base_t::unmap(); + } +}; -class wayfire_background_view : public wf::per_output_plugin_instance_t, - public wf::keyboard_interaction_t, - public wf::pointer_interaction_t, - public wf::touch_interaction_t +#if WF_HAS_XWAYLAND +// An adapter so that xwayland views can be used as background views +class wayfire_background_view_xwl : public wf::xwayland_view_base_t, public unmappable_view_t { - const std::string transformer_name = "background-view"; + wf::option_wrapper_t inhibit_input{"background-view/inhibit_input"}; + public: + wayfire_background_view_xwl(wlr_xwayland_surface *xw) : xwayland_view_base_t(xw) + { + this->kb_focus_enabled = !inhibit_input; + } + void map() + { + do_map(xw->surface, true); + } + + void bg_view_unmap() override + { + do_unmap(); + } +}; +#endif + +class wayfire_background_view : public wf::plugin_interface_t +{ /* The command option should be set to a client like mpv, projectM or * a single xscreensaver. */ @@ -78,125 +220,97 @@ class wayfire_background_view : public wf::per_output_plugin_instance_t, */ wf::option_wrapper_t app_id{"background-view/app_id"}; - /* When this option is true, keyboard, pointer and touch input will - * be inhibited on the background layer. - */ - wf::option_wrapper_t inhibit_input{"background-view/inhibit_input"}; - - /* To grab input on the background layer */ - std::unique_ptr grab; + // List of all background views assigned to an output + std::map> views; public: void init() override { - grab = std::make_unique( - "background-view", output, this, this, this); - - inhibit_input.set_callback(inhibit_input_changed); command.set_callback(option_changed); file.set_callback(option_changed); - - output->connect(&view_mapped); - output->connect(&view_detached); - - inhibit_input_changed(); + wf::get_core().connect(&on_view_pre_map); option_changed(); } - void handle_pointer_enter(wf::pointf_t position) override + void close_all_views() { - wf::get_core().set_cursor("default"); - } - - wf::config::option_base_t::updated_callback_t inhibit_input_changed = [=] () - { - if ((bool)inhibit_input) + for (auto& [output, view] : views) { - grab->grab_input(wf::scene::layer::BACKGROUND); - } else - { - grab->ungrab_input(); + view->close(); + view->on_unmap.disconnect(); + view->bg_view_unmap(); } - }; + + views.clear(); + } wf::config::option_base_t::updated_callback_t option_changed = [=] () { - if (views[output].view) - { - views[output].view->close(); - } - + close_all_views(); if (std::string(command).empty()) { return; } - views[output].view = nullptr; - wf::get_core().run(std::string( - command) + add_arg_if_not_empty(file)); + for (size_t i = 0; i < wf::get_core().output_layout->get_outputs().size(); i++) + { + wf::get_core().run(std::string(command) + add_arg_if_not_empty(file)); + } }; - void set_view_for_output(wayfire_toplevel_view view, wf::output_t *o) + void set_view_for_output(wayfire_toplevel_view view, wlr_surface *surface, wf::output_t *o) { - /* Move to the respective output */ - wf::move_view_to_output(view, o, false); - - /* A client should be used that can be resized to any size. - * If we set it fullscreen, the screensaver would be inhibited - * so we send a resize request that is the size of the output - */ - view->set_geometry(o->get_relative_geometry()); - - /* Set it as the background */ - view->get_wset()->remove_view(view); - wf::scene::readd_front(o->node_for_layer(wf::scene::layer::BACKGROUND), view->get_root_node()); - - /* Make it show on all workspaces in Expo, Cube, etc. */ - view->role = wf::VIEW_ROLE_DESKTOP_ENVIRONMENT; + std::shared_ptr new_view; + if (wlr_surface_is_xdg_surface(surface)) + { + auto toplevel = wlr_xdg_surface_from_wlr_surface(surface)->toplevel; + wlr_xdg_toplevel_set_size(toplevel, o->get_screen_size().width, o->get_screen_size().height); + new_view = unmappable_view_t::create(toplevel, o); + new_view->on_unmap.connect(&toplevel->base->events.unmap); +#if WF_HAS_XWAYLAND + } else if (wlr_surface_is_xwayland_surface(surface)) + { + auto xw = wlr_xwayland_surface_from_wlr_surface(surface); + wlr_xwayland_surface_configure(xw, o->get_layout_geometry().x, o->get_layout_geometry().y, + o->get_layout_geometry().width, o->get_layout_geometry().height); + new_view = unmappable_view_t::create(xw, o); + new_view->on_unmap.connect(&xw->events.unmap); + } else +#endif + { + LOGE("Failed to set background view: the view is neither xdg-toplevel nor a xwayland surface!"); + return; + } - /* Remember to close it later */ - views[o].view = view; + new_view->on_unmap.set_callback([this, o] (auto) + { + views[o]->bg_view_unmap(); + views.erase(o); + }); + views[o] = new_view; } - wf::signal::connection_t view_detached{[this] (wf::view_disappeared_signal* - ev) + wf::signal::connection_t on_view_pre_map = [=] (wf::view_pre_map_signal *ev) + { + auto view = wf::toplevel_cast(ev->view); + if (!view) { - auto view = ev->view; - - if (!view) - { - return; - } - - if (view == views[output].view) - { - views[output].view = nullptr; - } + return; } - }; - wf::signal::connection_t view_mapped{[this] (wf::view_mapped_signal *ev) + for (auto& o : wf::get_core().output_layout->get_outputs()) { - auto view = wf::toplevel_cast(ev->view); - - if (!view) + if (views.count(o)) { - return; + continue; } - for (auto& o : wf::get_core().output_layout->get_outputs()) + /* Try to match application identifier */ + if (std::string(app_id) == view->get_app_id()) { - if (views[o].view) - { - continue; - } - - /* Try to match application identifier */ - if (std::string(app_id) == view->get_app_id()) - { - set_view_for_output(view, o); - break; - } + set_view_for_output(view, ev->surface, o); + ev->override_implementation = true; + break; } } }; @@ -214,12 +328,8 @@ class wayfire_background_view : public wf::per_output_plugin_instance_t, void fini() override { - grab->ungrab_input(); - if (views[output].view) - { - views[output].view->close(); - } + close_all_views(); } }; -DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); +DECLARE_WAYFIRE_PLUGIN(wayfire_background_view); diff --git a/src/meson.build b/src/meson.build index d5c9b29..1f74066 100644 --- a/src/meson.build +++ b/src/meson.build @@ -9,7 +9,7 @@ autorotate = shared_module('autorotate-iio', 'autorotate-iio.cpp', endif background_view = shared_module('background-view', 'background-view.cpp', - dependencies: [wayfire], + dependencies: [wayfire, wf_protos], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) benchmark = shared_module('bench', 'bench.cpp',