diff --git a/metadata/panel.xml b/metadata/panel.xml index 0315f5cda..9da3261b9 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -478,6 +478,11 @@ Set to -1 to only run it by clicking the button. 192 1 + <_short>Workspace Switcher diff --git a/proto/meson.build b/proto/meson.build index 80f45a6c8..0d46e5ca0 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -16,6 +16,7 @@ wayland_scanner_client = generator( client_protocols = [ 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-screencopy.xml', wayfire.get_pkgconfig_variable('pkgdatadir') / 'unstable' / 'wayfire-shell-unstable-v2.xml', ] diff --git a/proto/wlr-screencopy.xml b/proto/wlr-screencopy.xml new file mode 100644 index 000000000..85b57d7ea --- /dev/null +++ b/proto/wlr-screencopy.xml @@ -0,0 +1,231 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Andri Yngvason + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a series of buffer events will be sent, each representing a + supported buffer type. The "buffer_done" event is sent afterwards to + indicate that all supported buffer types have been enumerated. The client + will then be able to send a "copy" request. If the capture is successful, + the compositor will send a "flags" event followed by a "ready" event. + + For objects version 2 or lower, wl_shm buffers are always supported, ie. + the "buffer" event is guaranteed to be sent. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about wl_shm buffer parameters that need to be + used for this frame. This event is sent once after the frame is created + if wl_shm buffers are supported. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have the + correct size, see zwlr_screencopy_frame_v1.buffer and + zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a + supported format. + + If the frame is successfully copied, "flags" and "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which the presentation took place. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + + + + Provides information about linux-dmabuf buffer parameters that need to + be used for this frame. This event is sent once after the frame is + created if linux-dmabuf buffers are supported. + + + + + + + + + This event is sent once after all buffer events have been sent. + + The client should proceed to create a buffer of one of the supported + types, and send a "copy" request. + + + + diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index aa5094234..bf6948bc6 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -437,6 +437,13 @@ void WayfirePanelApp::on_config_reload() bool WayfirePanelApp::panel_allowed_by_config(bool allowed, std::string output_name) { + std::string prefix = WayfireShellApp::get().live_preview_output_name; + + if (output_name.compare(0, prefix.length(), prefix) == 0) + { + return false; + } + if (allowed) { return std::string(*priv->panel_outputs).find("*") != std::string::npos || diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 12d6cfb20..e170938a5 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -7,11 +8,11 @@ #include #include +#include #include "toplevel.hpp" #include "window-list.hpp" #include "gtk-utils.hpp" -#include "panel.hpp" namespace { @@ -24,13 +25,208 @@ void set_image_from_icon(Gtk::Image& image, std::string app_id_list, int size, int scale); } +static int create_anon_file(off_t size) +{ + int fd = memfd_create("wf-live-preview", MFD_CLOEXEC); + + if (fd == -1) + { + perror("memfd_create"); + return 1; + } + + if (ftruncate(fd, size) == -1) + { + perror("ftruncate"); + close(fd); + return 1; + } + + return fd; +} + +void handle_frame_buffer(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + uint32_t format, + uint32_t width, + uint32_t height, + uint32_t stride) +{ + TooltipMedia *tooltip_media = (TooltipMedia*)data; + + size_t size = width * height * int(stride / width); + + if (tooltip_media->size != size) + { + if (tooltip_media->shm_data && tooltip_media->size) + { + if (munmap(tooltip_media->shm_data, tooltip_media->size) < 0) + { + perror("munmap failed"); + } + } + + tooltip_media->size = size; + auto anon_file = create_anon_file(size); + if (anon_file < 0) + { + perror("anon_file < 0"); + return; + } + + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, anon_file, 0); + if (data == MAP_FAILED) + { + perror("data == MAP_FAILED"); + close(anon_file); + return; + } + + wl_shm_pool *pool = wl_shm_create_pool(tooltip_media->window_list->shm, anon_file, size); + tooltip_media->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); + wl_shm_pool_destroy(pool); + close(anon_file); + tooltip_media->buffer_width = width; + tooltip_media->buffer_height = height; + tooltip_media->buffer_stride = stride; + tooltip_media->shm_data = data; + } + + zwlr_screencopy_frame_v1_copy(tooltip_media->frame, tooltip_media->buffer); +} + +void handle_frame_flags(void*, + struct zwlr_screencopy_frame_v1*, + uint32_t) +{} + +void handle_frame_ready(void *data, + struct zwlr_screencopy_frame_v1 *zwlr_screencopy_frame_v1, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec) +{ + TooltipMedia *tooltip_media = (TooltipMedia*)data; + + if (!tooltip_media->shm_data || !tooltip_media->size) + { + return; + } + + auto bytes = Glib::Bytes::create(tooltip_media->shm_data, tooltip_media->size); + + auto builder = Gdk::MemoryTextureBuilder::create(); + builder->set_bytes(bytes); + builder->set_width(tooltip_media->buffer_width); + builder->set_height(tooltip_media->buffer_height); + builder->set_stride(tooltip_media->buffer_stride); + builder->set_format(Gdk::MemoryFormat::R8G8B8A8); + + auto texture = builder->build(); + + tooltip_media->set_paintable(texture); +} + +void handle_frame_failed(void*, struct zwlr_screencopy_frame_v1*) +{} + +void handle_frame_damage(void*, + struct zwlr_screencopy_frame_v1*, + uint32_t, + uint32_t, + uint32_t, + uint32_t) +{} + +void handle_frame_linux_dmabuf(void*, + struct zwlr_screencopy_frame_v1*, + uint32_t, + uint32_t, + uint32_t) +{} + +void handle_frame_buffer_done(void*, struct zwlr_screencopy_frame_v1*) +{} + +static struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = +{ + handle_frame_buffer, + handle_frame_flags, + handle_frame_ready, + handle_frame_failed, + handle_frame_damage, + handle_frame_linux_dmabuf, + handle_frame_buffer_done, +}; + +void TooltipMedia::request_next_frame() +{ + if (this->frame) + { + zwlr_screencopy_frame_v1_destroy(this->frame); + this->frame = NULL; + } + + if (!this->window_list->window_list_live_preview_output || + !this->window_list->window_list_live_preview_output->output) + { + return; + } + + this->frame = zwlr_screencopy_manager_v1_capture_output(this->window_list->screencopy_manager, 0, + this->window_list->window_list_live_preview_output->output); + zwlr_screencopy_frame_v1_add_listener(this->frame, &screencopy_frame_listener, this); +} + +TooltipMedia::TooltipMedia(WayfireWindowList *window_list) +{ + this->window_list = window_list; + this->shm = window_list->shm; + + this->add_tick_callback([=] (const Glib::RefPtr& clock) + { + return this->on_tick(clock); + }); + + request_next_frame(); +} + +TooltipMedia::~TooltipMedia() +{ + if (this->frame) + { + zwlr_screencopy_frame_v1_destroy(this->frame); + this->frame = NULL; + } + + if (this->shm_data && this->size) + { + if (munmap(this->shm_data, this->size) < 0) + { + perror("munmap failed"); + } + } + + this->shm_data = NULL; + this->size = 0; +} + +bool TooltipMedia::on_tick(const Glib::RefPtr& clock) +{ + this->request_next_frame(); + return G_SOURCE_CONTINUE; +} + class WayfireToplevel::impl { zwlr_foreign_toplevel_handle_v1 *handle, *parent; std::vector children; uint32_t state; + uint64_t view_id; Gtk::Button button; + Gtk::Box custom_tooltip_content; + TooltipMedia *tooltip_media; Glib::RefPtr actions; Gtk::PopoverMenu popover; @@ -43,8 +239,8 @@ class WayfireToplevel::impl Gtk::Label label; // Gtk::PopoverMenu menu; Glib::RefPtr drag_gesture; - sigc::connection m_drag_timeout; std::vector signals; + sigc::connection button_leave_signal; Glib::ustring app_id, title; @@ -55,6 +251,7 @@ class WayfireToplevel::impl impl(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle) { + this->window_list = window_list; this->handle = handle; this->parent = nullptr; zwlr_foreign_toplevel_handle_v1_add_listener(handle, @@ -69,7 +266,6 @@ class WayfireToplevel::impl button_contents.set_hexpand(true); button_contents.set_spacing(5); button.set_child(button_contents); - button.set_tooltip_text("none"); label.set_ellipsize(Pango::EllipsizeMode::END); label.set_hexpand(true); @@ -158,12 +354,161 @@ class WayfireToplevel::impl button.add_controller(long_press); button.add_controller(click_gesture); - this->window_list = window_list; + auto motion_controller = Gtk::EventControllerMotion::create(); + button_leave_signal = motion_controller->signal_leave().connect([=] () + { + wf::json_t live_window_release_output_request; + live_window_release_output_request["method"] = "live_previews/release_output"; + this->window_list->ipc_client->send(live_window_release_output_request.serialize(), + [=] (wf::json_t data) + { + unset_tooltip_media(); + this->window_list->live_window_preview_view_id = 0; + if (data.serialize().find("error") != std::string::npos) + { + if (this->window_list->live_window_preview_tooltips) + { + std::cerr << + "Error releasing output for live preview stream! (is live-previews plugin disabled?)" + << + std::endl; + } + + this->window_list->enable_normal_tooltips_flag(true); + return; + } + }); + }); + button.add_controller(motion_controller); + motion_controller = Gtk::EventControllerMotion::create(); + signals.push_back(motion_controller->signal_enter().connect([=] (double x, double y) + { + wf::json_t live_window_preview_stream_request; + live_window_preview_stream_request["method"] = "live_previews/request_stream"; + wf::json_t view_id_int; + view_id_int["id"] = this->view_id; + live_window_preview_stream_request["data"] = view_id_int; + this->window_list->ipc_client->send(live_window_preview_stream_request.serialize(), + [=] (wf::json_t data) + { + if ((data.serialize().find("error") != std::string::npos) && + this->window_list->live_window_preview_tooltips) + { + std::cerr << data.serialize() << std::endl; + std::cerr << + "Error acquiring live preview stream. (is live-previews wayfire plugin enabled?)" << + std::endl; + this->window_list->enable_normal_tooltips_flag(true); + button.set_tooltip_text(title); + + return; + } + + set_tooltip_media(); + }); + })); + button.add_controller(motion_controller); + button.set_tooltip_text("none"); + this->tooltip_media = nullptr; + signals.push_back(button.signal_query_tooltip().connect([=] (int x, int y, bool keyboard_mode, + const Glib::RefPtr + & tooltip) + { + return query_tooltip(x, y, keyboard_mode, tooltip); + }, false)); + button.set_has_tooltip(true); + update_tooltip(); send_rectangle_hints(); set_state(0); // will set the appropriate button style } + void set_tooltip_media() + { + if (this->tooltip_media) + { + return; + } + + this->tooltip_media = Gtk::make_managed(this->window_list); + this->custom_tooltip_content.append(*this->tooltip_media); + } + + void unset_tooltip_media() + { + if (!this->tooltip_media) + { + return; + } + + this->tooltip_media->unparent(); + this->tooltip_media = nullptr; + } + + void update_tooltip() + { + wf::json_t ipc_methods_request; + ipc_methods_request["method"] = "list-methods"; + this->window_list->ipc_client->send(ipc_methods_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << "Error getting ipc methods list!" << std::endl; + this->window_list->enable_normal_tooltips_flag(true); + return; + } + + if ((data.serialize().find("live_previews/request_stream") == std::string::npos) || + (data.serialize().find("live_previews/release_output") == std::string::npos)) + { + unset_tooltip_media(); + if (this->window_list->live_window_preview_tooltips) + { + if (this->window_list->live_window_previews_opt) + { + std::cout << "wf-shell configuration [panel] option 'live_window_previews' is set to 'true' but live-previews wayfire plugin is disabled." << std::endl; + this->window_list->enable_normal_tooltips_flag(true); + } + + for (const auto& toplevel_button : this->window_list->toplevels) + { + if (toplevel_button.second && toplevel_button.second->pimpl) + { + toplevel_button.second->unset_tooltip_media(); + } + } + } + } else + { + set_tooltip_media(); + if (!this->window_list->live_window_preview_tooltips) + { + if (!this->window_list->live_window_previews_opt) + { + std::cout << "Detected live-previews plugin is enabled but wf-shell configuration [panel] option 'live_window_previews' is set to 'false'." << std::endl; + } else + { + std::cout << "Enabling live window preview tooltips." << std::endl; + } + + this->window_list->enable_normal_tooltips_flag(false); + for (const auto& toplevel_button : this->window_list->toplevels) + { + if (toplevel_button.second && toplevel_button.second->pimpl) + { + toplevel_button.second->set_tooltip_media(); + } + } + } + } + }); + if (!this->window_list->live_window_previews_enabled()) + { + this->window_list->normal_title_tooltips = true; + button.set_tooltip_text(title); + } + } + int grab_off_x; double grab_start_x, grab_start_y; double grab_abs_start_x; @@ -378,12 +723,77 @@ class WayfireToplevel::impl set_app_id(app_id); } + bool query_tooltip(int x, int y, bool keyboard_mode, const Glib::RefPtr& tooltip) + { + if (this->popover.is_visible()) + { + return false; + } + + update_tooltip(); + + if (!this->window_list->live_window_previews_enabled()) + { + if (!this->window_list->normal_title_tooltips) + { + std::cerr << + "Normal title tooltips enabled. To enable live window preview tooltips, make sure to set [panel] option 'live_window_previews = true' in wf-shell configuration and enable wayfire plugin live-previews" + << + std::endl; + this->window_list->enable_normal_tooltips_flag(true); + } + + tooltip->set_text(title); + return true; + } + + this->window_list->live_window_preview_view_id = this->view_id; + tooltip->set_custom(this->custom_tooltip_content); + + return true; + } + + uint64_t get_view_id_from_full_app_id(const std::string& app_id) + { + const std::string sub_str = "wf-ipc-"; + size_t pos = app_id.find(sub_str); + + if (pos != std::string::npos) + { + size_t suffix_start_index = pos + sub_str.length(); + if (suffix_start_index < app_id.length()) + { + try { + uint64_t view_id = std::stoi(app_id.substr(suffix_start_index, std::string::npos)); + return view_id; + } catch (...) + { + return 0; + } + } else + { + return 0; + } + } else + { + return 0; + } + } + void set_app_id(std::string app_id) { WfOption minimal_panel_height{"panel/minimal_height"}; this->app_id = app_id; IconProvider::set_image_from_icon(image, app_id, std::min(int(minimal_panel_height), 24), button.get_scale_factor()); + this->view_id = get_view_id_from_full_app_id(app_id); + if (this->view_id == 0) + { + std::cerr << "Failed to get view id from app_id. " << + "(Ensure 'app_id_mode' set to 'full' in wayfire " << + "[workarounds] and restart wf-panel or the applications " << + "in the window list)" << std::endl; + } } void send_rectangle_hints() @@ -414,7 +824,11 @@ class WayfireToplevel::impl void set_title(std::string title) { this->title = title; - button.set_tooltip_text(title); + if (!this->window_list->live_window_previews_enabled()) + { + button.set_tooltip_text(title); + } + label.set_text(title); } @@ -440,6 +854,7 @@ class WayfireToplevel::impl void remove_button() { + button_leave_signal.disconnect(); window_list->remove(button); send_rectangle_hints(); } @@ -486,10 +901,8 @@ class WayfireToplevel::impl ~impl() { gtk_widget_unparent(GTK_WIDGET(popover.gobj())); - if (m_drag_timeout) - { - m_drag_timeout.disconnect(); - } + + button_leave_signal.disconnect(); for (auto signal : signals) { @@ -529,7 +942,6 @@ WayfireToplevel::WayfireToplevel(WayfireWindowList *window_list, pimpl(new WayfireToplevel::impl(window_list, handle)) {} - std::vector& WayfireToplevel::get_children() { return pimpl->get_children(); @@ -545,7 +957,8 @@ void WayfireToplevel::send_rectangle_hint() return pimpl->send_rectangle_hint(); } -WayfireToplevel::~WayfireToplevel() = default; +WayfireToplevel::~WayfireToplevel() +{} using toplevel_t = zwlr_foreign_toplevel_handle_v1*; static void handle_toplevel_title(void *data, toplevel_t, const char *title) @@ -572,6 +985,16 @@ static void handle_toplevel_output_leave(void *data, toplevel_t, wl_output *outp impl->handle_output_leave(output); } +void WayfireToplevel::set_tooltip_media() +{ + pimpl->set_tooltip_media(); +} + +void WayfireToplevel::unset_tooltip_media() +{ + pimpl->unset_tooltip_media(); +} + /* wl_array_for_each isn't supported in C++, so we have to manually * get the data from wl_array, see: * @@ -612,9 +1035,7 @@ static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) } static void handle_toplevel_done(void *data, toplevel_t) -{ -// auto impl = static_cast (data); -} +{} static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t child) { diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 2796f3298..98251fceb 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -2,10 +2,15 @@ #include #include +#include #include #include #include +#include +#include #include +#include "wf-shell-app.hpp" +#include "panel.hpp" class WayfireWindowList; class WayfireWindowListBox; @@ -17,6 +22,28 @@ enum WayfireToplevelState WF_TOPLEVEL_STATE_MINIMIZED = (1 << 2), }; +class TooltipMedia : public Gtk::Picture +{ + public: + WayfireWindowList *window_list = NULL; + wl_shm *shm = NULL; + wl_buffer *buffer = NULL; + void *shm_data = NULL; + char *screencopy_data = NULL; + zwlr_screencopy_frame_v1 *frame = NULL; + + int buffer_width; + int buffer_height; + int buffer_stride; + size_t size = 0; + + TooltipMedia(WayfireWindowList *window_list); + ~TooltipMedia(); + + bool on_tick(const Glib::RefPtr& clock); + void request_next_frame(); +}; + /* Represents a single opened toplevel window. * It displays the window icon on all outputs' docks that it is visible on */ class WayfireToplevel @@ -29,6 +56,8 @@ class WayfireToplevel std::vector& get_children(); ~WayfireToplevel(); void set_hide_text(bool hide_text); + void set_tooltip_media(); + void unset_tooltip_media(); class impl; diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index fd6815447..ced865c10 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -25,14 +25,29 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name const char *interface, uint32_t version) { WayfireWindowList *window_list = (WayfireWindowList*)data; - if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) + + if (strcmp(interface, wl_output_interface.name) == 0) + { + wl_output *output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); + window_list->handle_new_wl_output(output); + } else if (strcmp(interface, wl_shm_interface.name) == 0) + { + window_list->shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, version); + } else if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { auto zwlr_toplevel_manager = (zwlr_foreign_toplevel_manager_v1*) wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, - std::min(version, 3u)); - + version); window_list->handle_toplevel_manager(zwlr_toplevel_manager); + zwlr_foreign_toplevel_manager_v1_add_listener(window_list->manager, + &toplevel_manager_v1_impl, window_list); + wl_display_roundtrip(window_list->display); + } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) + { + window_list->screencopy_manager = (zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, + &zwlr_screencopy_manager_v1_interface, + version); } } @@ -45,15 +60,161 @@ static struct wl_registry_listener registry_listener = ®istry_remove_object }; +void handle_output_geometry(void*, + struct wl_output*, + int32_t, + int32_t, + int32_t, + int32_t, + int32_t, + const char*, + const char*, + int32_t) +{} + +void handle_output_mode(void*, + struct wl_output*, + uint32_t, + int32_t, + int32_t, + int32_t) +{} + +void handle_output_done(void *data, struct wl_output*) +{} + +void handle_output_scale(void*, struct wl_output*, int32_t) +{} + +void handle_output_name(void *data, + struct wl_output *output, + const char *name) +{ + std::string live_preview_output_name = WayfireShellApp::get().live_preview_output_name; + WayfireWindowList *window_list = (WayfireWindowList*)data; + std::string output_name = name; + + if (output_name == live_preview_output_name) + { + window_list->window_list_live_preview_output = std::make_unique(); + window_list->window_list_live_preview_output->output = output; + window_list->window_list_live_preview_output->name = output_name; + } +} + +void handle_output_description(void*, struct wl_output*, const char*) +{} + +static struct wl_output_listener output_listener = +{ + handle_output_geometry, + handle_output_mode, + handle_output_done, + handle_output_scale, + handle_output_name, + handle_output_description, +}; + +void WayfireWindowList::destroy_window_list_live_preview_output() +{ + if (this->window_list_live_preview_output) + { + wl_output_destroy(this->window_list_live_preview_output->output); + this->window_list_live_preview_output.reset(); + this->window_list_live_preview_output = nullptr; + } +} + +void WayfireWindowList::handle_new_wl_output(wl_output *output) +{ + std::string live_preview_output_name = WayfireShellApp::get().live_preview_output_name; + + wl_output_add_listener(output, &output_listener, this); +} + +void WayfireWindowList::live_window_previews_plugin_check() +{ + wf::json_t ipc_methods_request; + ipc_methods_request["method"] = "list-methods"; + this->ipc_client->send(ipc_methods_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find( + "error") != std::string::npos) + { + std::cerr << "Error getting ipc methods list! (are ipc and ipc-rules plugins loaded?)" << std::endl; + this->enable_normal_tooltips_flag(true); + return; + } + + if ((data.serialize().find("live_previews/request_stream") == std::string::npos) || + (data.serialize().find( + "live_previews/release_output") == std::string::npos)) + { + std::cerr << "Did not find live-previews ipc methods in methods list. Disabling live window preview tooltips. (is the live-previews plugin enabled?)" << std::endl; + this->enable_normal_tooltips_flag( + true); + } else + { + if (!this->live_window_previews_opt) + { + std::cout << "Detected live-previews plugin is enabled but wf-shell configuration [panel] option 'live_window_previews' is set to 'false'." << std::endl; + this->enable_normal_tooltips_flag(true); + } else + { + std::cout << "Enabling live window preview tooltips." << std::endl; + this->enable_normal_tooltips_flag(false); + } + } + }); +} + +void WayfireWindowList::enable_ipc(bool enable) +{ + if (!this->ipc_client) + { + this->ipc_client = WayfirePanelApp::get().get_ipc_server_instance()->create_client(); + } + + if (!this->ipc_client) + { + std::cerr << + "Failed to connect to ipc. Live window previews will not be available. (are ipc and ipc-rules plugins loaded?)"; + } +} + +void WayfireWindowList::enable_normal_tooltips_flag(bool enable) +{ + this->normal_title_tooltips = enable; + this->live_window_preview_tooltips = !enable; +} + +bool WayfireWindowList::live_window_previews_enabled() +{ + return this->live_window_previews_opt && this->live_window_preview_tooltips && this->ipc_client; +} + void WayfireWindowList::init(Gtk::Box *container) { + enable_ipc(this->live_window_previews_opt); + live_window_previews_plugin_check(); + + this->live_window_previews_opt.set_callback([=] () + { + enable_ipc(this->live_window_previews_opt); + live_window_previews_plugin_check(); + }); + auto gdk_display = gdk_display_get_default(); auto display = gdk_wayland_display_get_wl_display(gdk_display); + this->display = display; + wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(display); + this->registry = registry; + if (!this->manager) { std::cerr << "Compositor doesn't support" << @@ -65,10 +226,6 @@ void WayfireWindowList::init(Gtk::Box *container) scrolled_window.add_css_class("window-list"); - wl_registry_destroy(registry); - zwlr_foreign_toplevel_manager_v1_add_listener(manager, - &toplevel_manager_v1_impl, this); - scrolled_window.set_hexpand(true); scrolled_window.set_child(*this); scrolled_window.set_propagate_natural_width(true); @@ -189,14 +346,11 @@ void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *han void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) { toplevels.erase(handle); - - /* No size adjustments necessary in this case */ - if (toplevels.size() == 0) - { - return; - } } +void WayfireWindowList::on_event(wf::json_t data) +{} + WayfireWindowList::WayfireWindowList(WayfireOutput *output) { this->output = output; @@ -211,5 +365,9 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) WayfireWindowList::~WayfireWindowList() { - zwlr_foreign_toplevel_manager_v1_destroy(manager); + destroy_window_list_live_preview_output(); + wl_registry_destroy(this->registry); + wl_shm_destroy(this->shm); + zwlr_foreign_toplevel_manager_v1_destroy(this->manager); + zwlr_screencopy_manager_v1_destroy(this->screencopy_manager); } diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 032876cbf..f5e6090e8 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -1,15 +1,22 @@ #pragma once -#include #include #include "../../widget.hpp" #include "toplevel.hpp" #include "layout.hpp" +#include "wf-ipc.hpp" + +class WayfireWindowListOutput +{ + public: + wl_output *output; + std::string name; +}; class WayfireToplevel; -class WayfireWindowList : public Gtk::Box, public WayfireWidget +class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubscriber { WfOption user_size{"panel/window_list_size"}; std::shared_ptr layout; @@ -18,7 +25,11 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget std::map> toplevels; + wl_display *display; + wl_registry *registry; + wl_shm *shm; zwlr_foreign_toplevel_manager_v1 *manager = NULL; + zwlr_screencopy_manager_v1 *screencopy_manager = NULL; WayfireOutput *output; Gtk::ScrolledWindow scrolled_window; @@ -65,6 +76,20 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget */ Gtk::Widget *get_widget_before(int x); + WfOption live_window_previews_opt{"panel/live_window_previews"}; + void handle_new_wl_output(wl_output *output); + void destroy_window_list_live_preview_output(); + std::unique_ptr window_list_live_preview_output = nullptr; + void on_event(wf::json_t data) override; + std::shared_ptr ipc_client; + bool live_window_preview_tooltips = false; + bool normal_title_tooltips = false; + void enable_normal_tooltips_flag(bool enable); + uint64_t live_window_preview_view_id = 0; + void live_window_previews_plugin_check(); + void enable_ipc(bool enable); + bool live_window_previews_enabled(); + private: int get_default_button_width(); int get_target_button_width(); diff --git a/src/util/background-gl.cpp b/src/util/background-gl.cpp index 53e06d654..bdf3f5c8a 100644 --- a/src/util/background-gl.cpp +++ b/src/util/background-gl.cpp @@ -235,18 +235,6 @@ void BackgroundGLArea::show_image(std::shared_ptr next_image) return; } - if ((window_width <= 0) || (window_height <= 0)) - { - /* Retry momentarily */ - Glib::signal_idle().connect([this, next_image] - () - { - show_image(next_image); - return G_SOURCE_REMOVE; - }); - return; - } - if (to_image) { from_image = to_image; @@ -342,7 +330,7 @@ bool BackgroundGLArea::render(const Glib::RefPtr& context) 1.0f, 1.0f, }; static float from_adj[4] = {0.0, 0.0, 1.0, 1.0}; - if (from_image) + if (from_image && from_image->adjustments) { from_adj[0] = from_image->adjustments->x; from_adj[1] = from_image->adjustments->y; @@ -351,7 +339,7 @@ bool BackgroundGLArea::render(const Glib::RefPtr& context) } static float to_adj[4] = {0.0, 0.0, 1.0, 1.0}; - if (to_image) + if (to_image && to_image->adjustments) { to_adj[0] = to_image->adjustments->x; to_adj[1] = to_image->adjustments->y; diff --git a/src/util/wf-shell-app.cpp b/src/util/wf-shell-app.cpp index 90e78d870..6108702c4 100644 --- a/src/util/wf-shell-app.cpp +++ b/src/util/wf-shell-app.cpp @@ -307,7 +307,9 @@ std::vector>*WayfireShellApp::get_wayfire_outputs } WayfireShellApp::WayfireShellApp() -{} +{ + live_preview_output_name = "live-preview"; +} void WayfireShellApp::init_app() { diff --git a/src/util/wf-shell-app.hpp b/src/util/wf-shell-app.hpp index 6862f84cf..dd2c77462 100644 --- a/src/util/wf-shell-app.hpp +++ b/src/util/wf-shell-app.hpp @@ -90,6 +90,7 @@ class WayfireShellApp virtual Gio::Application::Flags get_extra_application_flags(); virtual std::string get_application_name() = 0; std::vector> *get_wayfire_outputs(); + std::string live_preview_output_name; /** * WayfireShellApp is a singleton class.