From 686d72fb7535eec1d1489cf46184c38275adc06c Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 25 Feb 2026 08:38:51 -0700 Subject: [PATCH 01/13] panel: window-list: Implement live window preview tooltips Don't forget to install https://github.com/soreau/wf-live-previews and enable 'live-previews' plugin. Also set app_id_mode to 'full' in [workarounds]. --- proto/meson.build | 1 + src/panel/panel.cpp | 7 + src/panel/widgets/window-list/toplevel.cpp | 321 +++++++++++++++++- src/panel/widgets/window-list/toplevel.hpp | 27 ++ src/panel/widgets/window-list/window-list.cpp | 169 ++++++++- src/panel/widgets/window-list/window-list.hpp | 24 +- 6 files changed, 529 insertions(+), 20 deletions(-) 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/src/panel/panel.cpp b/src/panel/panel.cpp index aa5094234..0d05d864d 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 = "live-preview"; + + 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..752299136 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,6 +8,7 @@ #include #include +#include #include "toplevel.hpp" #include "window-list.hpp" @@ -24,13 +26,174 @@ 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) + { + tooltip_media->set_size_request(width, height); + 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; + + 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 (!window_list->wayfire_window_list_output->output) + { + return; + } + + this->frame = zwlr_screencopy_manager_v1_capture_output(window_list->screencopy_manager, 0, + window_list->wayfire_window_list_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->screencopy_manager = window_list->screencopy_manager; + + this->add_tick_callback([=] (const Glib::RefPtr& clock) + { + return this->on_tick(clock); + }); +} + +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; @@ -55,6 +218,15 @@ class WayfireToplevel::impl impl(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle) { + std::cout << "impl" << std::endl; + window_list->ipc_client = WayfirePanelApp::get().get_ipc_server_instance()->create_client(); + + if (!window_list->ipc_client) + { + std::cerr << + "Failed to connect to ipc. Live window previews will not be available. (are ipc and ipc-rules plugins loaded?)"; + } + this->handle = handle; this->parent = nullptr; zwlr_foreign_toplevel_handle_v1_add_listener(handle, @@ -69,7 +241,17 @@ class WayfireToplevel::impl button_contents.set_hexpand(true); button_contents.set_spacing(5); button.set_child(button_contents); - button.set_tooltip_text("none"); + // button.set_tooltip_text("none"); + tooltip_media = Gtk::make_managed(window_list); + this->custom_tooltip_content.append(*tooltip_media); + // button.set_hover_point(0, 0); + // button.set_hover_timeout(0); + 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); label.set_ellipsize(Pango::EllipsizeMode::END); label.set_hexpand(true); @@ -155,8 +337,24 @@ class WayfireToplevel::impl popover.popup(); } })); + auto motion_controller = Gtk::EventControllerMotion::create(); + 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) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error releasing output for live preview stream!" << std::endl; + } + }); + }); button.add_controller(long_press); button.add_controller(click_gesture); + button.add_controller(motion_controller); this->window_list = window_list; @@ -171,6 +369,7 @@ class WayfireToplevel::impl bool drag_paused() { + std::cout << __func__ << std::endl; /* * auto gseat = Gdk::Display::get_default()->get_default_seat()->get_wl_seat(); * //auto seat = gdk_wayland_seat_get_wl_seat(gseat->gobj()); @@ -181,6 +380,7 @@ class WayfireToplevel::impl void on_drag_begin(double _x, double _y) { + std::cout << __func__ << std::endl; // Set grab start, before transforming it to absolute position grab_start_x = _x; grab_start_y = _y; @@ -202,6 +402,7 @@ class WayfireToplevel::impl static constexpr int DRAG_THRESHOLD = 3; void on_drag_update(double _x, double y) { + std::cout << __func__ << std::endl; /* Window was not just clicked, but also dragged. Ignore the next click, * which is the one that happens when the drag gesture ends. */ set_ignore_next_click(); @@ -245,6 +446,7 @@ class WayfireToplevel::impl void on_drag_end(double _x, double _y) { + std::cout << __func__ << std::endl; int x = _x + grab_start_x; int y = _y + grab_start_y; int width = button.get_allocated_width(); @@ -278,6 +480,7 @@ class WayfireToplevel::impl void set_hide_text(bool hide_text) { + std::cout << __func__ << std::endl; if (hide_text) { label.hide(); @@ -289,6 +492,7 @@ class WayfireToplevel::impl void on_menu_minimize(Glib::VariantBase vb) { + std::cout << __func__ << std::endl; bool val = g_variant_get_boolean(vb.gobj()); send_rectangle_hint(); if (!val) @@ -302,6 +506,7 @@ class WayfireToplevel::impl void on_menu_maximize(Glib::VariantBase vb) { + std::cout << __func__ << std::endl; bool val = g_variant_get_boolean(vb.gobj()); if (!val) { @@ -314,12 +519,14 @@ class WayfireToplevel::impl void on_menu_close(Glib::VariantBase vb) { + std::cout << __func__ << std::endl; zwlr_foreign_toplevel_handle_v1_close(handle); } bool ignore_next_click = false; void set_ignore_next_click() { + std::cout << __func__ << std::endl; ignore_next_click = true; /* Make sure that the view doesn't show clicked on animations while @@ -330,6 +537,7 @@ class WayfireToplevel::impl void unset_ignore_next_click() { + std::cout << __func__ << std::endl; ignore_next_click = false; button.unset_state_flags(Gtk::StateFlags::SELECTED | Gtk::StateFlags::DROP_ACTIVE | Gtk::StateFlags::PRELIGHT); @@ -337,6 +545,7 @@ class WayfireToplevel::impl void on_clicked() { + std::cout << __func__ << std::endl; /* If the button was dragged, we don't want to register the click. * Subsequent clicks should be handled though. */ if (ignore_next_click) @@ -375,19 +584,89 @@ class WayfireToplevel::impl void on_scale_update() { + std::cout << __func__ << std::endl; set_app_id(app_id); } + bool query_tooltip(int x, int y, bool keyboard_mode, const Glib::RefPtr& tooltip) + { + std::cout << __func__ << std::endl; + if (!window_list->wayfire_window_list_output->output) + { + return false; + } + + if (this->popover.is_visible()) + { + return false; + } + + 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) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error acquiring live preview stream. (is live-previews wayfire plugin enabled?)" << std::endl; + } + }); + + 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) { + std::cout << __func__ << std::endl; 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. (Is app_id_mode set to 'full' in wayfire workarounds?)" << + std::endl; + } } void send_rectangle_hints() { + std::cout << __func__ << std::endl; for (const auto& toplevel_button : window_list->toplevels) { if (toplevel_button.second && toplevel_button.second->pimpl) @@ -399,6 +678,7 @@ class WayfireToplevel::impl void send_rectangle_hint() { + std::cout << __func__ << std::endl; auto panel = WayfirePanelApp::get().panel_for_wl_output(window_list->output->wo); auto w = button.get_width(); auto h = button.get_height(); @@ -413,39 +693,46 @@ class WayfireToplevel::impl void set_title(std::string title) { + std::cout << __func__ << std::endl; this->title = title; - button.set_tooltip_text(title); + // button.set_tooltip_text(title); label.set_text(title); } uint32_t get_state() { + std::cout << __func__ << std::endl; return this->state; } zwlr_foreign_toplevel_handle_v1 *get_parent() { + std::cout << __func__ << std::endl; return this->parent; } void set_parent(zwlr_foreign_toplevel_handle_v1 *parent) { + std::cout << __func__ << std::endl; this->parent = parent; } std::vector& get_children() { + std::cout << __func__ << std::endl; return this->children; } void remove_button() { + std::cout << __func__ << std::endl; window_list->remove(button); send_rectangle_hints(); } void set_classes(uint32_t state) { + std::cout << __func__ << std::endl; if (state & WF_TOPLEVEL_STATE_ACTIVATED) { button.add_css_class("activated"); @@ -479,12 +766,14 @@ class WayfireToplevel::impl void set_state(uint32_t state) { + std::cout << __func__ << std::endl; this->state = state; set_classes(state); } ~impl() { + std::cout << "~impl" << std::endl; gtk_widget_unparent(GTK_WIDGET(popover.gobj())); if (m_drag_timeout) { @@ -501,6 +790,7 @@ class WayfireToplevel::impl void handle_output_enter(wl_output *output) { + std::cout << __func__ << std::endl; if (this->parent) { return; @@ -515,6 +805,7 @@ class WayfireToplevel::impl void handle_output_leave(wl_output *output) { + std::cout << __func__ << std::endl; if (window_list->output->wo == output) { window_list->remove(button); @@ -527,47 +818,58 @@ class WayfireToplevel::impl WayfireToplevel::WayfireToplevel(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle) : pimpl(new WayfireToplevel::impl(window_list, handle)) -{} - +{ + std::cout << "WayfireToplevel::WayfireToplevel" << std::endl; +} std::vector& WayfireToplevel::get_children() { + std::cout << __func__ << std::endl; return pimpl->get_children(); } uint32_t WayfireToplevel::get_state() { + std::cout << __func__ << std::endl; return pimpl->get_state(); } void WayfireToplevel::send_rectangle_hint() { + std::cout << __func__ << std::endl; return pimpl->send_rectangle_hint(); } -WayfireToplevel::~WayfireToplevel() = default; +WayfireToplevel::~WayfireToplevel() +{ + std::cout << "WayfireToplevel::~WayfireToplevel" << std::endl; +} using toplevel_t = zwlr_foreign_toplevel_handle_v1*; static void handle_toplevel_title(void *data, toplevel_t, const char *title) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->set_title(title); } static void handle_toplevel_app_id(void *data, toplevel_t, const char *app_id) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->set_app_id(app_id); } static void handle_toplevel_output_enter(void *data, toplevel_t, wl_output *output) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->handle_output_enter(output); } static void handle_toplevel_output_leave(void *data, toplevel_t, wl_output *output) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->handle_output_leave(output); } @@ -579,6 +881,7 @@ static void handle_toplevel_output_leave(void *data, toplevel_t, wl_output *outp template static void array_for_each(wl_array *array, std::function func) { + std::cout << __func__ << std::endl; assert(array->size % sizeof(T) == 0); // do not use malformed arrays for (T *entry = (T*)array->data; (char*)entry < ((char*)array->data + array->size); entry++) { @@ -588,6 +891,7 @@ static void array_for_each(wl_array *array, std::function func) static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) { + std::cout << __func__ << std::endl; uint32_t flags = 0; array_for_each(state, [&flags] (uint32_t st) { @@ -613,11 +917,13 @@ static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) static void handle_toplevel_done(void *data, toplevel_t) { + std::cout << __func__ << std::endl; // auto impl = static_cast (data); } static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t child) { + std::cout << __func__ << std::endl; auto parent = impl->get_parent(); auto& parent_toplevel = impl->window_list->toplevels[parent]; if (child && parent && parent_toplevel) @@ -629,6 +935,7 @@ static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t chi static void handle_toplevel_closed(void *data, toplevel_t handle) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->remove_button(); remove_child_from_parent(impl, handle); @@ -637,6 +944,7 @@ static void handle_toplevel_closed(void *data, toplevel_t handle) static void handle_toplevel_parent(void *data, toplevel_t handle, toplevel_t parent) { + std::cout << __func__ << std::endl; auto impl = static_cast(data); if (!parent) { @@ -683,6 +991,7 @@ namespace { std::string tolower(std::string str) { + std::cout << __func__ << std::endl; for (auto& c : str) { c = std::tolower(c); @@ -698,6 +1007,7 @@ std::string tolower(std::string str) * The filename is either the app_id + ".desktop" or lower_app_id + ".desktop" */ Icon get_from_desktop_app_info(std::string app_id) { + std::cout << __func__ << std::endl; Glib::RefPtr app_info; std::vector prefixes = { @@ -743,6 +1053,7 @@ Icon get_from_desktop_app_info(std::string app_id) void set_image_from_icon(Gtk::Image& image, std::string app_id_list, int size, int scale) { + std::cout << __func__ << std::endl; std::string app_id; std::istringstream stream(app_id_list); diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 2796f3298..469ab1279 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -2,9 +2,12 @@ #include #include +#include #include #include #include +#include +#include #include class WayfireWindowList; @@ -17,6 +20,30 @@ 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; + zwlr_screencopy_manager_v1 *screencopy_manager = NULL; + + int buffer_width = 200; + int buffer_height = 200; + int buffer_stride = 200 * 4; + 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 diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index fd6815447..cc849826b 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -9,6 +9,7 @@ static void handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, zwlr_foreign_toplevel_handle_v1 *toplevel) { + std::cout << __func__ << std::endl; WayfireWindowList *window_list = (WayfireWindowList*)data; window_list->handle_new_toplevel(toplevel); } @@ -24,20 +25,69 @@ zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_v1_impl = { static void registry_add_object(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + std::cout << __func__ << ": " << interface << std::endl; WayfireWindowList *window_list = (WayfireWindowList*)data; - if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) + + window_list->foreign_toplevel_manager_id = name; + window_list->foreign_toplevel_version = version; + + if (strcmp(interface, wl_output_interface.name) == 0) + { + std::cout << "New wl_output: " << name << std::endl; + wl_output *output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); + window_list->handle_new_wl_output(data, registry, name, interface, version, 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); + std::cout << __func__ << ": " << interface << std::endl; 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); } } static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) -{} +{ + std::cout << __func__ << ": " << name << std::endl; +} + +void WayfireWindowList::wl_output_enter() +{ + std::cout << __func__ << ": " << " output name: " << this->wayfire_window_list_output->name << std::endl; + // self.set_live_preview_output(None) + // if name.startswith("live-preview"): + // self.set_live_preview_output(output) + // return + // if name.startswith("live-preview"): + // return + // if not self.my_output and name == self.get_root().monitor_name: + // self.wl_surface = None + // if self.get_root().get_native().get_surface(): + // wl_surface_ptr = gtk.gdk_wayland_surface_get_wl_surface( + // ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer( + // self.get_root().get_native().get_surface().__gpointer__, None))) + // self.wl_surface = WlSurface() + // self.wl_surface._ptr = wl_surface_ptr + // self.my_output = output + // else: + // toplevel_buttons = self.toplevel_buttons.copy() + // for handle in toplevel_buttons: + // button = self.toplevel_buttons[handle] + // if output != button.output: + // self.foreign_toplevel_closed(handle) +} static struct wl_registry_listener registry_listener = { @@ -45,15 +95,103 @@ 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*, struct wl_output*) +{} + +void handle_output_scale(void*, struct wl_output*, int32_t) +{} + +void handle_output_name(void *data, + struct wl_output *wl_output, + const char *name) +{ + std::cout << __func__ << ": " << data << std::endl; + std::unique_ptr *wayfire_window_list_output = + (std::unique_ptr*)data; + (*wayfire_window_list_output)->name = std::string(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::handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, + const char *interface, uint32_t version, wl_output *output) +{ + std::cout << __func__ << std::endl; + this->wayfire_window_list_output = std::make_unique(); + this->wayfire_window_list_output->output = output; + this->wayfire_window_list_output->name.clear(); + wl_output_add_listener(output, &output_listener, &this->wayfire_window_list_output); + // while (this->wayfire_window_list_output->name.empty()) + // { + // wl_display_dispatch(display); + // } + this->wl_output_enter(); + // if not output.name.startswith("live-preview"): + // if self.foreign_toplevel_manager: + // self.foreign_toplevel_manager.stop() + // self.wl_display.roundtrip() + // if self.foreign_toplevel_manager: + // self.destroy_foreign_toplevel_manager() + // self.foreign_toplevel_manager = self.registry.bind(self.foreign_toplevel_manager_id, + // ZwlrForeignToplevelManagerV1, self.foreign_toplevel_version) + // self.foreign_toplevel_manager.id = self.foreign_toplevel_manager_id + // self.wl_globals.append(self.foreign_toplevel_manager) + // self.foreign_toplevel_manager.dispatcher["toplevel"] = self.on_new_toplevel + // self.foreign_toplevel_manager.dispatcher["finished"] = self.on_foreign_toplevel_manager_finish + // self.wl_display.roundtrip() + // print(f"Monitor {output.name} ({interface}) connected", file=sys.stderr) +} + void WayfireWindowList::init(Gtk::Box *container) { + std::cout << __func__ << std::endl; 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); + while (!this->manager || this->wayfire_window_list_output->name.empty()) + { + std::cout << "looping" << std::endl; + wl_display_dispatch(display); + } + + std::cout << "output name: " << this->wayfire_window_list_output->name << std::endl; if (!this->manager) { std::cerr << "Compositor doesn't support" << @@ -65,9 +203,7 @@ 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); + // wl_registry_destroy(registry); scrolled_window.set_hexpand(true); scrolled_window.set_child(*this); @@ -78,6 +214,7 @@ void WayfireWindowList::init(Gtk::Box *container) void WayfireWindowList::set_top_widget(Gtk::Widget *top) { + std::cout << __func__ << std::endl; this->layout->top_widget = top; if (layout->top_widget) @@ -92,6 +229,7 @@ void WayfireWindowList::set_top_widget(Gtk::Widget *top) void WayfireWindowList::set_top_x(int x) { + std::cout << __func__ << std::endl; /* Make sure that the widget doesn't go outside of the box */ if (this->layout->top_widget) { @@ -116,6 +254,7 @@ void WayfireWindowList::set_top_x(int x) int WayfireWindowList::get_absolute_position(int x, Gtk::Widget& ref) { + std::cout << __func__ << std::endl; auto w = &ref; while (w && w != this) { @@ -129,6 +268,7 @@ int WayfireWindowList::get_absolute_position(int x, Gtk::Widget& ref) Gtk::Widget*WayfireWindowList::get_widget_before(int x) { + std::cout << __func__ << std::endl; Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; /* Widgets are stored bottom to top, so we will return the bottom-most @@ -155,6 +295,7 @@ Gtk::Widget*WayfireWindowList::get_widget_before(int x) Gtk::Widget*WayfireWindowList::get_widget_at(int x) { + std::cout << __func__ << std::endl; Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; /* Widgets are stored bottom to top, so we will return the bottom-most @@ -178,27 +319,28 @@ Gtk::Widget*WayfireWindowList::get_widget_at(int x) void WayfireWindowList::handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager) { + std::cout << __func__ << std::endl; this->manager = manager; } void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle) { + std::cout << __func__ << std::endl; toplevels[handle] = std::unique_ptr(new WayfireToplevel(this, handle)); } void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) { + std::cout << __func__ << std::endl; 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) { + std::cout << "WayfireWindowList::WayfireWindowList" << std::endl; this->output = output; layout = std::make_shared(this); @@ -211,5 +353,6 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) WayfireWindowList::~WayfireWindowList() { + std::cout << "WayfireWindowList::~WayfireWindowList" << std::endl; zwlr_foreign_toplevel_manager_v1_destroy(manager); } diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 032876cbf..e49cc88dc 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,12 +25,19 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget std::map> toplevels; + wl_display *display; + wl_shm *shm; zwlr_foreign_toplevel_manager_v1 *manager = NULL; + zwlr_screencopy_manager_v1 *screencopy_manager = NULL; WayfireOutput *output; Gtk::ScrolledWindow scrolled_window; + uint32_t foreign_toplevel_manager_id; + uint32_t foreign_toplevel_version; + WayfireWindowList(WayfireOutput *output); virtual ~WayfireWindowList(); + std::unique_ptr wayfire_window_list_output; void handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager); void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); @@ -65,6 +79,12 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget */ Gtk::Widget *get_widget_before(int x); + void handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, const char *interface, + uint32_t version, wl_output *output); + void wl_output_enter(); + void on_event(wf::json_t data) override; + std::shared_ptr ipc_client; + private: int get_default_button_width(); int get_target_button_width(); From c759c63522ce5d41db43229a7be2b199f43feb96 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 26 Feb 2026 06:30:59 -0700 Subject: [PATCH 02/13] panel: window-list: Fix live window previews toggling between normal tooltips and previews --- src/panel/widgets/window-list/toplevel.cpp | 212 ++++++++++++++---- src/panel/widgets/window-list/toplevel.hpp | 3 + src/panel/widgets/window-list/window-list.cpp | 74 +++--- src/panel/widgets/window-list/window-list.hpp | 2 + 4 files changed, 205 insertions(+), 86 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 752299136..555e96d3e 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -13,7 +13,6 @@ #include "toplevel.hpp" #include "window-list.hpp" #include "gtk-utils.hpp" -#include "panel.hpp" namespace { @@ -207,6 +206,7 @@ class WayfireToplevel::impl // Gtk::PopoverMenu menu; Glib::RefPtr drag_gesture; sigc::connection m_drag_timeout; + sigc::connection query_tooltip_signal; std::vector signals; Glib::ustring app_id, title; @@ -218,15 +218,7 @@ class WayfireToplevel::impl impl(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle) { - std::cout << "impl" << std::endl; - window_list->ipc_client = WayfirePanelApp::get().get_ipc_server_instance()->create_client(); - - if (!window_list->ipc_client) - { - std::cerr << - "Failed to connect to ipc. Live window previews will not be available. (are ipc and ipc-rules plugins loaded?)"; - } - + this->window_list = window_list; this->handle = handle; this->parent = nullptr; zwlr_foreign_toplevel_handle_v1_add_listener(handle, @@ -241,17 +233,6 @@ class WayfireToplevel::impl button_contents.set_hexpand(true); button_contents.set_spacing(5); button.set_child(button_contents); - // button.set_tooltip_text("none"); - tooltip_media = Gtk::make_managed(window_list); - this->custom_tooltip_content.append(*tooltip_media); - // button.set_hover_point(0, 0); - // button.set_hover_timeout(0); - 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); label.set_ellipsize(Pango::EllipsizeMode::END); label.set_hexpand(true); @@ -337,31 +318,121 @@ class WayfireToplevel::impl popover.popup(); } })); - auto motion_controller = Gtk::EventControllerMotion::create(); - 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) - { - if (data.serialize().find("error") != std::string::npos) - { - std::cerr << data.serialize() << std::endl; - std::cerr << "Error releasing output for live preview stream!" << std::endl; - } - }); - }); button.add_controller(long_press); button.add_controller(click_gesture); - button.add_controller(motion_controller); - this->window_list = window_list; + this->query_tooltip_signal = + 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); + button.set_tooltip_text("none"); + this->tooltip_media = nullptr; + 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); + this->window_list->live_window_preview_tooltips = true; + } + + 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 << data.serialize() << std::endl; + std::cerr << "Error getting ipc methods list!" << std::endl; + 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) + { + std::cerr << "Disabling live window preview tooltips." << std::endl; + this->window_list->live_window_preview_tooltips = false; + 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) + { + std::cout << "Enabling live window preview tooltips." << std::endl; + this->window_list->live_window_preview_tooltips = true; + for (const auto& toplevel_button : this->window_list->toplevels) + { + if (toplevel_button.second && toplevel_button.second->pimpl) + { + toplevel_button.second->set_tooltip_media(); + } + } + } + } + }); + if (window_list->live_window_preview_tooltips) + { + auto motion_controller = Gtk::EventControllerMotion::create(); + 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) + { + this->window_list->live_window_preview_view_id = 0; + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error releasing output for live preview stream!" << std::endl; + this->window_list->live_window_preview_tooltips = false; + return; + } + }); + }); + button.add_controller(motion_controller); + } else + { + button.set_tooltip_text(title); + } + } + int grab_off_x; double grab_start_x, grab_start_y; double grab_abs_start_x; @@ -591,14 +662,31 @@ class WayfireToplevel::impl bool query_tooltip(int x, int y, bool keyboard_mode, const Glib::RefPtr& tooltip) { std::cout << __func__ << std::endl; - if (!window_list->wayfire_window_list_output->output) + if (this->popover.is_visible()) { return false; } - if (this->popover.is_visible()) + update_tooltip(); + + if (this->window_list->live_window_preview_tooltips) { - return false; + if (!this->window_list->wayfire_window_list_output->output) + { + std::cerr << "Live window preview tooltips output not found." << std::endl; + tooltip->set_text(title); + return true; + } + } else + { + tooltip->set_text(title); + return true; + } + + if (this->window_list->live_window_preview_view_id == this->view_id) + { + tooltip->set_custom(this->custom_tooltip_content); + return true; } wf::json_t live_window_preview_stream_request; @@ -609,14 +697,23 @@ class WayfireToplevel::impl this->window_list->ipc_client->send(live_window_preview_stream_request.serialize(), [=] (wf::json_t data) { - if (data.serialize().find("error") != std::string::npos) + 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; + if (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->live_window_preview_tooltips = false; + button.set_tooltip_text(title); + } + + return; } - }); - tooltip->set_custom(this->custom_tooltip_content); + this->window_list->live_window_preview_view_id = this->view_id; + tooltip->set_custom(this->custom_tooltip_content); + }); return true; } @@ -695,7 +792,11 @@ class WayfireToplevel::impl { std::cout << __func__ << std::endl; this->title = title; - // button.set_tooltip_text(title); + if (!this->window_list->live_window_preview_tooltips) + { + button.set_tooltip_text(title); + } + label.set_text(title); } @@ -780,6 +881,11 @@ class WayfireToplevel::impl m_drag_timeout.disconnect(); } + if (query_tooltip_signal) + { + query_tooltip_signal.disconnect(); + } + for (auto signal : signals) { signal.disconnect(); @@ -874,6 +980,18 @@ static void handle_toplevel_output_leave(void *data, toplevel_t, wl_output *outp impl->handle_output_leave(output); } +void WayfireToplevel::set_tooltip_media() +{ + std::cout << __func__ << std::endl; + pimpl->set_tooltip_media(); +} + +void WayfireToplevel::unset_tooltip_media() +{ + std::cout << __func__ << std::endl; + 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: * diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 469ab1279..09c926ec9 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "panel.hpp" class WayfireWindowList; class WayfireWindowListBox; @@ -56,6 +57,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 cc849826b..acbb0167a 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -66,27 +66,6 @@ static void registry_remove_object(void *data, struct wl_registry *registry, uin void WayfireWindowList::wl_output_enter() { std::cout << __func__ << ": " << " output name: " << this->wayfire_window_list_output->name << std::endl; - // self.set_live_preview_output(None) - // if name.startswith("live-preview"): - // self.set_live_preview_output(output) - // return - // if name.startswith("live-preview"): - // return - // if not self.my_output and name == self.get_root().monitor_name: - // self.wl_surface = None - // if self.get_root().get_native().get_surface(): - // wl_surface_ptr = gtk.gdk_wayland_surface_get_wl_surface( - // ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer( - // self.get_root().get_native().get_surface().__gpointer__, None))) - // self.wl_surface = WlSurface() - // self.wl_surface._ptr = wl_surface_ptr - // self.my_output = output - // else: - // toplevel_buttons = self.toplevel_buttons.copy() - // for handle in toplevel_buttons: - // button = self.toplevel_buttons[handle] - // if output != button.output: - // self.foreign_toplevel_closed(handle) } static struct wl_registry_listener registry_listener = @@ -152,29 +131,46 @@ void WayfireWindowList::handle_new_wl_output(void *data, wl_registry *registry, this->wayfire_window_list_output->output = output; this->wayfire_window_list_output->name.clear(); wl_output_add_listener(output, &output_listener, &this->wayfire_window_list_output); - // while (this->wayfire_window_list_output->name.empty()) - // { - // wl_display_dispatch(display); - // } this->wl_output_enter(); - // if not output.name.startswith("live-preview"): - // if self.foreign_toplevel_manager: - // self.foreign_toplevel_manager.stop() - // self.wl_display.roundtrip() - // if self.foreign_toplevel_manager: - // self.destroy_foreign_toplevel_manager() - // self.foreign_toplevel_manager = self.registry.bind(self.foreign_toplevel_manager_id, - // ZwlrForeignToplevelManagerV1, self.foreign_toplevel_version) - // self.foreign_toplevel_manager.id = self.foreign_toplevel_manager_id - // self.wl_globals.append(self.foreign_toplevel_manager) - // self.foreign_toplevel_manager.dispatcher["toplevel"] = self.on_new_toplevel - // self.foreign_toplevel_manager.dispatcher["finished"] = self.on_foreign_toplevel_manager_finish - // self.wl_display.roundtrip() - // print(f"Monitor {output.name} ({interface}) connected", file=sys.stderr) } void WayfireWindowList::init(Gtk::Box *container) { + 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?)"; + } + + wf::json_t ipc_methods_request; + ipc_methods_request["method"] = "list-methods"; // "live_previews/release_output"; + this->ipc_client->send(ipc_methods_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting ipc methods list!" << std::endl; + 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->live_window_preview_tooltips = false; + } else + { + std::cout << "Enabling live window preview tooltips." << std::endl; + this->live_window_preview_tooltips = true; + } + }); + std::cout << __func__ << std::endl; auto gdk_display = gdk_display_get_default(); auto display = gdk_wayland_display_get_wl_display(gdk_display); diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index e49cc88dc..9ce80cf83 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -84,6 +84,8 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs void wl_output_enter(); void on_event(wf::json_t data) override; std::shared_ptr ipc_client; + bool live_window_preview_tooltips = false; + uint64_t live_window_preview_view_id = 0; private: int get_default_button_width(); From edbb32cdc903d78ee1058faab217bfe42d7aedb0 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 26 Feb 2026 06:36:47 -0700 Subject: [PATCH 03/13] panel: window-list: Remove debug spew --- src/panel/widgets/window-list/toplevel.cpp | 48 +------------------ src/panel/widgets/window-list/window-list.cpp | 24 +--------- src/panel/widgets/window-list/window-list.hpp | 1 - 3 files changed, 2 insertions(+), 71 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 555e96d3e..398dddf38 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -440,7 +440,6 @@ class WayfireToplevel::impl bool drag_paused() { - std::cout << __func__ << std::endl; /* * auto gseat = Gdk::Display::get_default()->get_default_seat()->get_wl_seat(); * //auto seat = gdk_wayland_seat_get_wl_seat(gseat->gobj()); @@ -451,7 +450,6 @@ class WayfireToplevel::impl void on_drag_begin(double _x, double _y) { - std::cout << __func__ << std::endl; // Set grab start, before transforming it to absolute position grab_start_x = _x; grab_start_y = _y; @@ -473,7 +471,6 @@ class WayfireToplevel::impl static constexpr int DRAG_THRESHOLD = 3; void on_drag_update(double _x, double y) { - std::cout << __func__ << std::endl; /* Window was not just clicked, but also dragged. Ignore the next click, * which is the one that happens when the drag gesture ends. */ set_ignore_next_click(); @@ -517,7 +514,6 @@ class WayfireToplevel::impl void on_drag_end(double _x, double _y) { - std::cout << __func__ << std::endl; int x = _x + grab_start_x; int y = _y + grab_start_y; int width = button.get_allocated_width(); @@ -551,7 +547,6 @@ class WayfireToplevel::impl void set_hide_text(bool hide_text) { - std::cout << __func__ << std::endl; if (hide_text) { label.hide(); @@ -563,7 +558,6 @@ class WayfireToplevel::impl void on_menu_minimize(Glib::VariantBase vb) { - std::cout << __func__ << std::endl; bool val = g_variant_get_boolean(vb.gobj()); send_rectangle_hint(); if (!val) @@ -577,7 +571,6 @@ class WayfireToplevel::impl void on_menu_maximize(Glib::VariantBase vb) { - std::cout << __func__ << std::endl; bool val = g_variant_get_boolean(vb.gobj()); if (!val) { @@ -590,14 +583,12 @@ class WayfireToplevel::impl void on_menu_close(Glib::VariantBase vb) { - std::cout << __func__ << std::endl; zwlr_foreign_toplevel_handle_v1_close(handle); } bool ignore_next_click = false; void set_ignore_next_click() { - std::cout << __func__ << std::endl; ignore_next_click = true; /* Make sure that the view doesn't show clicked on animations while @@ -608,7 +599,6 @@ class WayfireToplevel::impl void unset_ignore_next_click() { - std::cout << __func__ << std::endl; ignore_next_click = false; button.unset_state_flags(Gtk::StateFlags::SELECTED | Gtk::StateFlags::DROP_ACTIVE | Gtk::StateFlags::PRELIGHT); @@ -616,7 +606,6 @@ class WayfireToplevel::impl void on_clicked() { - std::cout << __func__ << std::endl; /* If the button was dragged, we don't want to register the click. * Subsequent clicks should be handled though. */ if (ignore_next_click) @@ -655,13 +644,11 @@ class WayfireToplevel::impl void on_scale_update() { - std::cout << __func__ << std::endl; set_app_id(app_id); } bool query_tooltip(int x, int y, bool keyboard_mode, const Glib::RefPtr& tooltip) { - std::cout << __func__ << std::endl; if (this->popover.is_visible()) { return false; @@ -747,7 +734,6 @@ class WayfireToplevel::impl void set_app_id(std::string app_id) { - std::cout << __func__ << std::endl; WfOption minimal_panel_height{"panel/minimal_height"}; this->app_id = app_id; IconProvider::set_image_from_icon(image, app_id, @@ -763,7 +749,6 @@ class WayfireToplevel::impl void send_rectangle_hints() { - std::cout << __func__ << std::endl; for (const auto& toplevel_button : window_list->toplevels) { if (toplevel_button.second && toplevel_button.second->pimpl) @@ -775,7 +760,6 @@ class WayfireToplevel::impl void send_rectangle_hint() { - std::cout << __func__ << std::endl; auto panel = WayfirePanelApp::get().panel_for_wl_output(window_list->output->wo); auto w = button.get_width(); auto h = button.get_height(); @@ -790,7 +774,6 @@ class WayfireToplevel::impl void set_title(std::string title) { - std::cout << __func__ << std::endl; this->title = title; if (!this->window_list->live_window_preview_tooltips) { @@ -802,38 +785,32 @@ class WayfireToplevel::impl uint32_t get_state() { - std::cout << __func__ << std::endl; return this->state; } zwlr_foreign_toplevel_handle_v1 *get_parent() { - std::cout << __func__ << std::endl; return this->parent; } void set_parent(zwlr_foreign_toplevel_handle_v1 *parent) { - std::cout << __func__ << std::endl; this->parent = parent; } std::vector& get_children() { - std::cout << __func__ << std::endl; return this->children; } void remove_button() { - std::cout << __func__ << std::endl; window_list->remove(button); send_rectangle_hints(); } void set_classes(uint32_t state) { - std::cout << __func__ << std::endl; if (state & WF_TOPLEVEL_STATE_ACTIVATED) { button.add_css_class("activated"); @@ -867,14 +844,12 @@ class WayfireToplevel::impl void set_state(uint32_t state) { - std::cout << __func__ << std::endl; this->state = state; set_classes(state); } ~impl() { - std::cout << "~impl" << std::endl; gtk_widget_unparent(GTK_WIDGET(popover.gobj())); if (m_drag_timeout) { @@ -896,7 +871,6 @@ class WayfireToplevel::impl void handle_output_enter(wl_output *output) { - std::cout << __func__ << std::endl; if (this->parent) { return; @@ -911,7 +885,6 @@ class WayfireToplevel::impl void handle_output_leave(wl_output *output) { - std::cout << __func__ << std::endl; if (window_list->output->wo == output) { window_list->remove(button); @@ -930,19 +903,16 @@ WayfireToplevel::WayfireToplevel(WayfireWindowList *window_list, std::vector& WayfireToplevel::get_children() { - std::cout << __func__ << std::endl; return pimpl->get_children(); } uint32_t WayfireToplevel::get_state() { - std::cout << __func__ << std::endl; return pimpl->get_state(); } void WayfireToplevel::send_rectangle_hint() { - std::cout << __func__ << std::endl; return pimpl->send_rectangle_hint(); } @@ -954,41 +924,35 @@ WayfireToplevel::~WayfireToplevel() using toplevel_t = zwlr_foreign_toplevel_handle_v1*; static void handle_toplevel_title(void *data, toplevel_t, const char *title) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->set_title(title); } static void handle_toplevel_app_id(void *data, toplevel_t, const char *app_id) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->set_app_id(app_id); } static void handle_toplevel_output_enter(void *data, toplevel_t, wl_output *output) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->handle_output_enter(output); } static void handle_toplevel_output_leave(void *data, toplevel_t, wl_output *output) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->handle_output_leave(output); } void WayfireToplevel::set_tooltip_media() { - std::cout << __func__ << std::endl; pimpl->set_tooltip_media(); } void WayfireToplevel::unset_tooltip_media() { - std::cout << __func__ << std::endl; pimpl->unset_tooltip_media(); } @@ -999,7 +963,6 @@ void WayfireToplevel::unset_tooltip_media() template static void array_for_each(wl_array *array, std::function func) { - std::cout << __func__ << std::endl; assert(array->size % sizeof(T) == 0); // do not use malformed arrays for (T *entry = (T*)array->data; (char*)entry < ((char*)array->data + array->size); entry++) { @@ -1009,7 +972,6 @@ static void array_for_each(wl_array *array, std::function func) static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) { - std::cout << __func__ << std::endl; uint32_t flags = 0; array_for_each(state, [&flags] (uint32_t st) { @@ -1034,14 +996,10 @@ static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) } static void handle_toplevel_done(void *data, toplevel_t) -{ - std::cout << __func__ << std::endl; -// auto impl = static_cast (data); -} +{} static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t child) { - std::cout << __func__ << std::endl; auto parent = impl->get_parent(); auto& parent_toplevel = impl->window_list->toplevels[parent]; if (child && parent && parent_toplevel) @@ -1053,7 +1011,6 @@ static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t chi static void handle_toplevel_closed(void *data, toplevel_t handle) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); impl->remove_button(); remove_child_from_parent(impl, handle); @@ -1062,7 +1019,6 @@ static void handle_toplevel_closed(void *data, toplevel_t handle) static void handle_toplevel_parent(void *data, toplevel_t handle, toplevel_t parent) { - std::cout << __func__ << std::endl; auto impl = static_cast(data); if (!parent) { @@ -1125,7 +1081,6 @@ std::string tolower(std::string str) * The filename is either the app_id + ".desktop" or lower_app_id + ".desktop" */ Icon get_from_desktop_app_info(std::string app_id) { - std::cout << __func__ << std::endl; Glib::RefPtr app_info; std::vector prefixes = { @@ -1171,7 +1126,6 @@ Icon get_from_desktop_app_info(std::string app_id) void set_image_from_icon(Gtk::Image& image, std::string app_id_list, int size, int scale) { - std::cout << __func__ << std::endl; std::string app_id; std::istringstream stream(app_id_list); diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index acbb0167a..f7234c52f 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -9,7 +9,6 @@ static void handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, zwlr_foreign_toplevel_handle_v1 *toplevel) { - std::cout << __func__ << std::endl; WayfireWindowList *window_list = (WayfireWindowList*)data; window_list->handle_new_toplevel(toplevel); } @@ -59,14 +58,7 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name } static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) -{ - std::cout << __func__ << ": " << name << std::endl; -} - -void WayfireWindowList::wl_output_enter() -{ - std::cout << __func__ << ": " << " output name: " << this->wayfire_window_list_output->name << std::endl; -} +{} static struct wl_registry_listener registry_listener = { @@ -104,7 +96,6 @@ void handle_output_name(void *data, struct wl_output *wl_output, const char *name) { - std::cout << __func__ << ": " << data << std::endl; std::unique_ptr *wayfire_window_list_output = (std::unique_ptr*)data; (*wayfire_window_list_output)->name = std::string(name); @@ -126,12 +117,10 @@ static struct wl_output_listener output_listener = void WayfireWindowList::handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version, wl_output *output) { - std::cout << __func__ << std::endl; this->wayfire_window_list_output = std::make_unique(); this->wayfire_window_list_output->output = output; this->wayfire_window_list_output->name.clear(); wl_output_add_listener(output, &output_listener, &this->wayfire_window_list_output); - this->wl_output_enter(); } void WayfireWindowList::init(Gtk::Box *container) @@ -171,7 +160,6 @@ void WayfireWindowList::init(Gtk::Box *container) } }); - std::cout << __func__ << std::endl; auto gdk_display = gdk_display_get_default(); auto display = gdk_wayland_display_get_wl_display(gdk_display); @@ -210,7 +198,6 @@ void WayfireWindowList::init(Gtk::Box *container) void WayfireWindowList::set_top_widget(Gtk::Widget *top) { - std::cout << __func__ << std::endl; this->layout->top_widget = top; if (layout->top_widget) @@ -225,7 +212,6 @@ void WayfireWindowList::set_top_widget(Gtk::Widget *top) void WayfireWindowList::set_top_x(int x) { - std::cout << __func__ << std::endl; /* Make sure that the widget doesn't go outside of the box */ if (this->layout->top_widget) { @@ -250,7 +236,6 @@ void WayfireWindowList::set_top_x(int x) int WayfireWindowList::get_absolute_position(int x, Gtk::Widget& ref) { - std::cout << __func__ << std::endl; auto w = &ref; while (w && w != this) { @@ -264,7 +249,6 @@ int WayfireWindowList::get_absolute_position(int x, Gtk::Widget& ref) Gtk::Widget*WayfireWindowList::get_widget_before(int x) { - std::cout << __func__ << std::endl; Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; /* Widgets are stored bottom to top, so we will return the bottom-most @@ -291,7 +275,6 @@ Gtk::Widget*WayfireWindowList::get_widget_before(int x) Gtk::Widget*WayfireWindowList::get_widget_at(int x) { - std::cout << __func__ << std::endl; Gtk::Allocation given_point{x, get_allocated_height() / 2, 1, 1}; /* Widgets are stored bottom to top, so we will return the bottom-most @@ -315,19 +298,16 @@ Gtk::Widget*WayfireWindowList::get_widget_at(int x) void WayfireWindowList::handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager) { - std::cout << __func__ << std::endl; this->manager = manager; } void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle) { - std::cout << __func__ << std::endl; toplevels[handle] = std::unique_ptr(new WayfireToplevel(this, handle)); } void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) { - std::cout << __func__ << std::endl; toplevels.erase(handle); } @@ -336,7 +316,6 @@ void WayfireWindowList::on_event(wf::json_t data) WayfireWindowList::WayfireWindowList(WayfireOutput *output) { - std::cout << "WayfireWindowList::WayfireWindowList" << std::endl; this->output = output; layout = std::make_shared(this); @@ -349,6 +328,5 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) WayfireWindowList::~WayfireWindowList() { - std::cout << "WayfireWindowList::~WayfireWindowList" << std::endl; zwlr_foreign_toplevel_manager_v1_destroy(manager); } diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 9ce80cf83..3fc0e5548 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -81,7 +81,6 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs void handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version, wl_output *output); - void wl_output_enter(); void on_event(wf::json_t data) override; std::shared_ptr ipc_client; bool live_window_preview_tooltips = false; From 1b6e27bc17a0e3eb0b90efa3a2b1ef0730c5deea Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 26 Feb 2026 14:16:58 -0700 Subject: [PATCH 04/13] panel: window-list: Survive tty switch and other perils with live window previews enabled --- src/panel/widgets/window-list/window-list.cpp | 5 +++-- src/panel/widgets/window-list/window-list.hpp | 1 + src/util/background-gl.cpp | 16 ++-------------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index f7234c52f..1df89d443 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -169,6 +169,8 @@ void WayfireWindowList::init(Gtk::Box *container) wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(display); + this->registry = registry; + while (!this->manager || this->wayfire_window_list_output->name.empty()) { std::cout << "looping" << std::endl; @@ -187,8 +189,6 @@ void WayfireWindowList::init(Gtk::Box *container) scrolled_window.add_css_class("window-list"); - // wl_registry_destroy(registry); - scrolled_window.set_hexpand(true); scrolled_window.set_child(*this); scrolled_window.set_propagate_natural_width(true); @@ -328,5 +328,6 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) WayfireWindowList::~WayfireWindowList() { + wl_registry_destroy(this->registry); zwlr_foreign_toplevel_manager_v1_destroy(manager); } diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 3fc0e5548..5c4bf571d 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -26,6 +26,7 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs std::unique_ptr> toplevels; wl_display *display; + wl_registry *registry; wl_shm *shm; zwlr_foreign_toplevel_manager_v1 *manager = NULL; zwlr_screencopy_manager_v1 *screencopy_manager = NULL; 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; From 6bd8285573f4930474eca90b8d06cc5378d622e9 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 26 Feb 2026 20:33:33 -0700 Subject: [PATCH 05/13] panel: window-list: Add live_window_previews option --- metadata/panel.xml | 5 + src/panel/widgets/window-list/toplevel.cpp | 109 ++++++++++-------- src/panel/widgets/window-list/window-list.cpp | 76 ++++++++---- src/panel/widgets/window-list/window-list.hpp | 8 +- 4 files changed, 131 insertions(+), 67 deletions(-) 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/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 398dddf38..6c73cce66 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -321,6 +321,33 @@ class WayfireToplevel::impl button.add_controller(long_press); button.add_controller(click_gesture); + auto motion_controller = Gtk::EventControllerMotion::create(); + 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) + { + 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); + button.set_tooltip_text("none"); + this->tooltip_media = nullptr; this->query_tooltip_signal = button.signal_query_tooltip().connect([=] (int x, int y, bool keyboard_mode, const Glib::RefPtr @@ -329,8 +356,6 @@ class WayfireToplevel::impl return query_tooltip(x, y, keyboard_mode, tooltip); }, false); button.set_has_tooltip(true); - button.set_tooltip_text("none"); - this->tooltip_media = nullptr; update_tooltip(); send_rectangle_hints(); @@ -346,7 +371,6 @@ class WayfireToplevel::impl this->tooltip_media = Gtk::make_managed(this->window_list); this->custom_tooltip_content.append(*this->tooltip_media); - this->window_list->live_window_preview_tooltips = true; } void unset_tooltip_media() @@ -368,8 +392,8 @@ class WayfireToplevel::impl { if (data.serialize().find("error") != std::string::npos) { - std::cerr << data.serialize() << std::endl; std::cerr << "Error getting ipc methods list!" << std::endl; + this->window_list->enable_normal_tooltips_flag(true); return; } @@ -379,8 +403,12 @@ class WayfireToplevel::impl unset_tooltip_media(); if (this->window_list->live_window_preview_tooltips) { - std::cerr << "Disabling live window preview tooltips." << std::endl; - this->window_list->live_window_preview_tooltips = false; + 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) @@ -394,8 +422,15 @@ class WayfireToplevel::impl set_tooltip_media(); if (!this->window_list->live_window_preview_tooltips) { - std::cout << "Enabling live window preview tooltips." << std::endl; - this->window_list->live_window_preview_tooltips = true; + 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) @@ -406,29 +441,9 @@ class WayfireToplevel::impl } } }); - if (window_list->live_window_preview_tooltips) - { - auto motion_controller = Gtk::EventControllerMotion::create(); - 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) - { - this->window_list->live_window_preview_view_id = 0; - if (data.serialize().find("error") != std::string::npos) - { - std::cerr << data.serialize() << std::endl; - std::cerr << "Error releasing output for live preview stream!" << std::endl; - this->window_list->live_window_preview_tooltips = false; - return; - } - }); - }); - button.add_controller(motion_controller); - } else + if (!this->window_list->live_window_previews_enabled()) { + this->window_list->normal_title_tooltips = true; button.set_tooltip_text(title); } } @@ -656,16 +671,26 @@ class WayfireToplevel::impl update_tooltip(); - if (this->window_list->live_window_preview_tooltips) + if (this->window_list->live_window_previews_enabled()) { if (!this->window_list->wayfire_window_list_output->output) { std::cerr << "Live window preview tooltips output not found." << std::endl; tooltip->set_text(title); + this->window_list->enable_normal_tooltips_flag(true); return true; } } else { + 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->normal_title_tooltips = true; + } + tooltip->set_text(title); return true; } @@ -687,13 +712,10 @@ class WayfireToplevel::impl if ((data.serialize().find("error") != std::string::npos) && this->window_list->live_window_preview_tooltips) { - if (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->live_window_preview_tooltips = false; - button.set_tooltip_text(title); - } + 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; } @@ -775,7 +797,7 @@ class WayfireToplevel::impl void set_title(std::string title) { this->title = title; - if (!this->window_list->live_window_preview_tooltips) + if (!this->window_list->live_window_previews_enabled()) { button.set_tooltip_text(title); } @@ -897,9 +919,7 @@ class WayfireToplevel::impl WayfireToplevel::WayfireToplevel(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle) : pimpl(new WayfireToplevel::impl(window_list, handle)) -{ - std::cout << "WayfireToplevel::WayfireToplevel" << std::endl; -} +{} std::vector& WayfireToplevel::get_children() { @@ -917,9 +937,7 @@ void WayfireToplevel::send_rectangle_hint() } WayfireToplevel::~WayfireToplevel() -{ - std::cout << "WayfireToplevel::~WayfireToplevel" << std::endl; -} +{} using toplevel_t = zwlr_foreign_toplevel_handle_v1*; static void handle_toplevel_title(void *data, toplevel_t, const char *title) @@ -1065,7 +1083,6 @@ namespace { std::string tolower(std::string str) { - std::cout << __func__ << std::endl; for (auto& c : str) { c = std::tolower(c); diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 1df89d443..49ed3c014 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -24,7 +24,6 @@ zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_v1_impl = { static void registry_add_object(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - std::cout << __func__ << ": " << interface << std::endl; WayfireWindowList *window_list = (WayfireWindowList*)data; window_list->foreign_toplevel_manager_id = name; @@ -32,7 +31,6 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name if (strcmp(interface, wl_output_interface.name) == 0) { - std::cout << "New wl_output: " << name << std::endl; wl_output *output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); window_list->handle_new_wl_output(data, registry, name, interface, version, output); } else if (strcmp(interface, wl_shm_interface.name) == 0) @@ -123,27 +121,18 @@ void WayfireWindowList::handle_new_wl_output(void *data, wl_registry *registry, wl_output_add_listener(output, &output_listener, &this->wayfire_window_list_output); } -void WayfireWindowList::init(Gtk::Box *container) +void WayfireWindowList::live_window_previews_plugin_check() { - 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?)"; - } - wf::json_t ipc_methods_request; - ipc_methods_request["method"] = "list-methods"; // "live_previews/release_output"; + 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) + if (data.serialize().find( + "error") != std::string::npos) { - std::cerr << data.serialize() << std::endl; - std::cerr << "Error getting ipc methods list!" << std::endl; + std::cerr << "Error getting ipc methods list! (are ipc and ipc-rules plugins loaded?)" << std::endl; + this->live_window_preview_tooltips = false; + this->normal_title_tooltips = true; return; } @@ -153,12 +142,59 @@ void WayfireWindowList::init(Gtk::Box *container) { 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->live_window_preview_tooltips = false; + this->normal_title_tooltips = true; } else { - std::cout << "Enabling live window preview tooltips." << std::endl; - this->live_window_preview_tooltips = true; + 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->live_window_preview_tooltips = false; + this->normal_title_tooltips = true; + } else + { + std::cout << "Enabling live window preview tooltips." << std::endl; + this->live_window_preview_tooltips = true; + this->normal_title_tooltips = 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); diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 5c4bf571d..7f8b49adb 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -80,12 +80,18 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs */ Gtk::Widget *get_widget_before(int x); + WfOption live_window_previews_opt{"panel/live_window_previews"}; void handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version, wl_output *output); void on_event(wf::json_t data) override; std::shared_ptr ipc_client; - bool live_window_preview_tooltips = false; + 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(); From cc9aca0c4681e1c16e60d335ea1d59f34ce3ceed Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 26 Feb 2026 21:00:29 -0700 Subject: [PATCH 06/13] proto: Add wlr-screencopy.xml --- proto/wlr-screencopy.xml | 231 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 proto/wlr-screencopy.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. + + + + From 68dd7d7648248afa2edafe457942410e53414678 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Fri, 27 Feb 2026 02:29:02 -0700 Subject: [PATCH 07/13] panel: window-list: Clean up --- src/panel/panel.cpp | 2 +- src/panel/widgets/window-list/toplevel.cpp | 24 ++---- src/panel/widgets/window-list/toplevel.hpp | 8 +- src/panel/widgets/window-list/window-list.cpp | 79 +++++++++++-------- src/panel/widgets/window-list/window-list.hpp | 10 +-- src/util/wf-shell-app.cpp | 4 +- src/util/wf-shell-app.hpp | 1 + 7 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 0d05d864d..bf6948bc6 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -437,7 +437,7 @@ void WayfirePanelApp::on_config_reload() bool WayfirePanelApp::panel_allowed_by_config(bool allowed, std::string output_name) { - std::string prefix = "live-preview"; + std::string prefix = WayfireShellApp::get().live_preview_output_name; if (output_name.compare(0, prefix.length(), prefix) == 0) { diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 6c73cce66..fa599ea68 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -155,13 +155,14 @@ void TooltipMedia::request_next_frame() this->frame = NULL; } - if (!window_list->wayfire_window_list_output->output) + 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(window_list->screencopy_manager, 0, - window_list->wayfire_window_list_output->output); + 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); } @@ -169,7 +170,6 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list) { this->window_list = window_list; this->shm = window_list->shm; - this->screencopy_manager = window_list->screencopy_manager; this->add_tick_callback([=] (const Glib::RefPtr& clock) { @@ -671,16 +671,7 @@ class WayfireToplevel::impl update_tooltip(); - if (this->window_list->live_window_previews_enabled()) - { - if (!this->window_list->wayfire_window_list_output->output) - { - std::cerr << "Live window preview tooltips output not found." << std::endl; - tooltip->set_text(title); - this->window_list->enable_normal_tooltips_flag(true); - return true; - } - } else + if (!this->window_list->live_window_previews_enabled()) { if (!this->window_list->normal_title_tooltips) { @@ -688,7 +679,7 @@ class WayfireToplevel::impl "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->normal_title_tooltips = true; + this->window_list->enable_normal_tooltips_flag(true); } tooltip->set_text(title); @@ -764,7 +755,8 @@ class WayfireToplevel::impl if (this->view_id == 0) { std::cerr << - "Failed to get view id from app_id. (Is app_id_mode set to 'full' in wayfire workarounds?)" << + "Failed to get view id from app_id. (Is 'app_id_mode' set to 'full' in wayfire [workarounds]?)" + << std::endl; } } diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 09c926ec9..33113ee67 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -9,6 +9,7 @@ #include #include #include +#include "wf-shell-app.hpp" #include "panel.hpp" class WayfireWindowList; @@ -30,11 +31,10 @@ class TooltipMedia : public Gtk::Picture void *shm_data = NULL; char *screencopy_data = NULL; zwlr_screencopy_frame_v1 *frame = NULL; - zwlr_screencopy_manager_v1 *screencopy_manager = NULL; - int buffer_width = 200; - int buffer_height = 200; - int buffer_stride = 200 * 4; + int buffer_width; + int buffer_height; + int buffer_stride; size_t size = 0; TooltipMedia(WayfireWindowList *window_list); diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 49ed3c014..7929650ba 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -26,13 +26,10 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name { WayfireWindowList *window_list = (WayfireWindowList*)data; - window_list->foreign_toplevel_manager_id = name; - window_list->foreign_toplevel_version = version; - 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(data, registry, name, interface, version, output); + 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); @@ -42,7 +39,6 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version); - std::cout << __func__ << ": " << interface << std::endl; window_list->handle_toplevel_manager(zwlr_toplevel_manager); zwlr_foreign_toplevel_manager_v1_add_listener(window_list->manager, &toplevel_manager_v1_impl, window_list); @@ -84,19 +80,29 @@ void handle_output_mode(void*, int32_t) {} -void handle_output_done(void*, struct wl_output*) -{} +void handle_output_done(void *data, struct wl_output*) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + window_list->wl_outputs_done = true; +} void handle_output_scale(void*, struct wl_output*, int32_t) {} void handle_output_name(void *data, - struct wl_output *wl_output, + struct wl_output *output, const char *name) { - std::unique_ptr *wayfire_window_list_output = - (std::unique_ptr*)data; - (*wayfire_window_list_output)->name = std::string(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*) @@ -112,13 +118,27 @@ static struct wl_output_listener output_listener = handle_output_description, }; -void WayfireWindowList::handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, - const char *interface, uint32_t version, wl_output *output) +void WayfireWindowList::destroy_window_list_live_preview_output() { - this->wayfire_window_list_output = std::make_unique(); - this->wayfire_window_list_output->output = output; - this->wayfire_window_list_output->name.clear(); - wl_output_add_listener(output, &output_listener, &this->wayfire_window_list_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); + + this->wl_outputs_done = false; + while (!this->wl_outputs_done) + { + wl_display_dispatch(this->display); + } } void WayfireWindowList::live_window_previews_plugin_check() @@ -131,8 +151,7 @@ void WayfireWindowList::live_window_previews_plugin_check() "error") != std::string::npos) { std::cerr << "Error getting ipc methods list! (are ipc and ipc-rules plugins loaded?)" << std::endl; - this->live_window_preview_tooltips = false; - this->normal_title_tooltips = true; + this->enable_normal_tooltips_flag(true); return; } @@ -141,20 +160,18 @@ void WayfireWindowList::live_window_previews_plugin_check() "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->live_window_preview_tooltips = false; - this->normal_title_tooltips = true; + 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->live_window_preview_tooltips = false; - this->normal_title_tooltips = true; + this->enable_normal_tooltips_flag(true); } else { std::cout << "Enabling live window preview tooltips." << std::endl; - this->live_window_preview_tooltips = true; - this->normal_title_tooltips = false; + this->enable_normal_tooltips_flag(false); } } }); @@ -207,13 +224,6 @@ void WayfireWindowList::init(Gtk::Box *container) this->registry = registry; - while (!this->manager || this->wayfire_window_list_output->name.empty()) - { - std::cout << "looping" << std::endl; - wl_display_dispatch(display); - } - - std::cout << "output name: " << this->wayfire_window_list_output->name << std::endl; if (!this->manager) { std::cerr << "Compositor doesn't support" << @@ -364,6 +374,9 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) WayfireWindowList::~WayfireWindowList() { + destroy_window_list_live_preview_output(); wl_registry_destroy(this->registry); - zwlr_foreign_toplevel_manager_v1_destroy(manager); + 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 7f8b49adb..a1425598f 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -33,12 +33,8 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs WayfireOutput *output; Gtk::ScrolledWindow scrolled_window; - uint32_t foreign_toplevel_manager_id; - uint32_t foreign_toplevel_version; - WayfireWindowList(WayfireOutput *output); virtual ~WayfireWindowList(); - std::unique_ptr wayfire_window_list_output; void handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager); void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); @@ -81,8 +77,10 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs Gtk::Widget *get_widget_before(int x); WfOption live_window_previews_opt{"panel/live_window_previews"}; - void handle_new_wl_output(void *data, wl_registry *registry, uint32_t name, const char *interface, - uint32_t version, wl_output *output); + void handle_new_wl_output(wl_output *output); + void destroy_window_list_live_preview_output(); + bool wl_outputs_done; + 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; 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. From 9249abe54001f6331aef3b1561a3b7d38fd04b96 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sun, 1 Mar 2026 14:59:10 -0700 Subject: [PATCH 08/13] panel: window-list: Call munmap to clean up mmap'ed shm data --- src/panel/widgets/window-list/toplevel.cpp | 11 +++++++++++ src/panel/widgets/window-list/toplevel.hpp | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index fa599ea68..d973f3867 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -177,6 +177,17 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list) }); } +TooltipMedia::~TooltipMedia() +{ + 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(); diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 33113ee67..98251fceb 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -38,8 +38,7 @@ class TooltipMedia : public Gtk::Picture size_t size = 0; TooltipMedia(WayfireWindowList *window_list); - ~TooltipMedia() - {} + ~TooltipMedia(); bool on_tick(const Glib::RefPtr& clock); void request_next_frame(); From bd6da12f7ae4c8be3ddad50163c2c6ea9177f07c Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sun, 1 Mar 2026 15:26:21 -0700 Subject: [PATCH 09/13] panel: window-list: More cleanup Be more explicit about TooltipMedia life cycle. Also destroy screencopy frame in TooltipMedia destructor. --- src/panel/widgets/window-list/toplevel.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index d973f3867..c08c018c2 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -101,6 +101,11 @@ void handle_frame_ready(void *data, { 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(); @@ -179,9 +184,18 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list) TooltipMedia::~TooltipMedia() { - if (munmap(this->shm_data, this->size) < 0) + if (this->frame) { - perror("munmap failed"); + 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; @@ -340,6 +354,7 @@ class WayfireToplevel::impl 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) { @@ -722,6 +737,7 @@ class WayfireToplevel::impl return; } + set_tooltip_media(); this->window_list->live_window_preview_view_id = this->view_id; tooltip->set_custom(this->custom_tooltip_content); }); From 1bb10bd2356735db02a94eea1ad1ca97ba0187a9 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sun, 1 Mar 2026 16:33:02 -0700 Subject: [PATCH 10/13] panel: window-list: Request live preview stream on enter motion controller This ultimately fixes empty tooltips from appaering for a couple of frames before the live preview frame contents are ready. --- src/panel/widgets/window-list/toplevel.cpp | 62 ++++++++++--------- src/panel/widgets/window-list/window-list.cpp | 11 +--- src/panel/widgets/window-list/window-list.hpp | 1 - 3 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index c08c018c2..6f2c68053 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -58,7 +58,6 @@ void handle_frame_buffer(void *data, if (tooltip_media->size != size) { - tooltip_media->set_size_request(width, height); tooltip_media->size = size; auto anon_file = create_anon_file(size); if (anon_file < 0) @@ -180,6 +179,8 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list) { return this->on_tick(clock); }); + + request_next_frame(); } TooltipMedia::~TooltipMedia() @@ -372,6 +373,34 @@ class WayfireToplevel::impl }); }); button.add_controller(motion_controller); + motion_controller = Gtk::EventControllerMotion::create(); + 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; this->query_tooltip_signal = @@ -712,35 +741,8 @@ class WayfireToplevel::impl return true; } - if (this->window_list->live_window_preview_view_id == this->view_id) - { - tooltip->set_custom(this->custom_tooltip_content); - return true; - } - - 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(); - this->window_list->live_window_preview_view_id = this->view_id; - tooltip->set_custom(this->custom_tooltip_content); - }); + this->window_list->live_window_preview_view_id = this->view_id; + tooltip->set_custom(this->custom_tooltip_content); return true; } diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 7929650ba..ced865c10 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -81,10 +81,7 @@ void handle_output_mode(void*, {} void handle_output_done(void *data, struct wl_output*) -{ - WayfireWindowList *window_list = (WayfireWindowList*)data; - window_list->wl_outputs_done = true; -} +{} void handle_output_scale(void*, struct wl_output*, int32_t) {} @@ -133,12 +130,6 @@ 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); - - this->wl_outputs_done = false; - while (!this->wl_outputs_done) - { - wl_display_dispatch(this->display); - } } void WayfireWindowList::live_window_previews_plugin_check() diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index a1425598f..f5e6090e8 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -79,7 +79,6 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs WfOption live_window_previews_opt{"panel/live_window_previews"}; void handle_new_wl_output(wl_output *output); void destroy_window_list_live_preview_output(); - bool wl_outputs_done; std::unique_ptr window_list_live_preview_output = nullptr; void on_event(wf::json_t data) override; std::shared_ptr ipc_client; From d398c90cc462c443ca3bffc10cd4213063d09f8d Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sun, 1 Mar 2026 17:06:17 -0700 Subject: [PATCH 11/13] panel: window-list: Clean up signals better --- src/panel/widgets/window-list/toplevel.cpp | 28 ++++++++-------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 6f2c68053..ca900ed03 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -231,9 +231,8 @@ class WayfireToplevel::impl Gtk::Label label; // Gtk::PopoverMenu menu; Glib::RefPtr drag_gesture; - sigc::connection m_drag_timeout; - sigc::connection query_tooltip_signal; std::vector signals; + sigc::connection button_leave_signal; Glib::ustring app_id, title; @@ -348,7 +347,7 @@ class WayfireToplevel::impl button.add_controller(click_gesture); auto motion_controller = Gtk::EventControllerMotion::create(); - motion_controller->signal_leave().connect([=] () + 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"; @@ -374,7 +373,7 @@ class WayfireToplevel::impl }); button.add_controller(motion_controller); motion_controller = Gtk::EventControllerMotion::create(); - motion_controller->signal_enter().connect([=] (double x, double y) + 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"; @@ -399,17 +398,16 @@ class WayfireToplevel::impl set_tooltip_media(); }); - }); + })); button.add_controller(motion_controller); button.set_tooltip_text("none"); this->tooltip_media = nullptr; - this->query_tooltip_signal = - button.signal_query_tooltip().connect([=] (int x, int y, bool keyboard_mode, - const Glib::RefPtr - & tooltip) + 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); + }, false)); button.set_has_tooltip(true); update_tooltip(); @@ -848,6 +846,7 @@ class WayfireToplevel::impl void remove_button() { + button_leave_signal.disconnect(); window_list->remove(button); send_rectangle_hints(); } @@ -894,15 +893,8 @@ class WayfireToplevel::impl ~impl() { gtk_widget_unparent(GTK_WIDGET(popover.gobj())); - if (m_drag_timeout) - { - m_drag_timeout.disconnect(); - } - if (query_tooltip_signal) - { - query_tooltip_signal.disconnect(); - } + button_leave_signal.disconnect(); for (auto signal : signals) { From 8767d85084382123b7793725d30153fbf0009cf1 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Mon, 2 Mar 2026 18:02:35 -0700 Subject: [PATCH 12/13] panel: window-list: Call munmap to free mmap'ed data when the buffer size changes --- src/panel/widgets/window-list/toplevel.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index ca900ed03..548f516e5 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -58,6 +58,14 @@ void handle_frame_buffer(void *data, 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) From 0e1e39b376f4d61a8a1e3c7b9f9b8d6d2dd2e888 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 3 Mar 2026 17:02:05 -0700 Subject: [PATCH 13/13] panel: window-list: Clarify message about setting app_id_mode to full --- src/panel/widgets/window-list/toplevel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 548f516e5..e170938a5 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -789,10 +789,10 @@ class WayfireToplevel::impl 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. (Is 'app_id_mode' set to 'full' in wayfire [workarounds]?)" - << - std::endl; + 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; } }