From 3f6a0e317fa4594e26429f26581becd453aed93e Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 13:58:22 -0600 Subject: [PATCH 01/10] window-list: Switch live-previews to use ext-copy-capture instead of wlr-screencopy --- proto/meson.build | 1 - proto/wlr-screencopy.xml | 231 ------- src/panel/widgets/window-list/toplevel.cpp | 589 ++++++++---------- src/panel/widgets/window-list/toplevel.hpp | 26 +- src/panel/widgets/window-list/window-list.cpp | 106 +++- src/panel/widgets/window-list/window-list.hpp | 17 +- 6 files changed, 381 insertions(+), 589 deletions(-) delete mode 100644 proto/wlr-screencopy.xml diff --git a/proto/meson.build b/proto/meson.build index 28d4037a..de6234c9 100644 --- a/proto/meson.build +++ b/proto/meson.build @@ -20,7 +20,6 @@ client_protocols = [ [wl_protocol_dir, 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml'], [wl_protocol_dir, 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml'], 'wlr-foreign-toplevel-management-unstable-v1.xml', - 'wlr-screencopy.xml', wayfire.get_pkgconfig_variable('pkgdatadir') / 'unstable' / 'wayfire-shell-unstable-v2.xml', ] diff --git a/proto/wlr-screencopy.xml b/proto/wlr-screencopy.xml deleted file mode 100644 index 85b57d7e..00000000 --- a/proto/wlr-screencopy.xml +++ /dev/null @@ -1,231 +0,0 @@ - - - - Copyright © 2018 Simon Ser - Copyright © 2019 Andri Yngvason - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - This protocol allows clients to ask the compositor to copy part of the - screen content to a client buffer. - - Warning! The protocol described in this file is experimental and - backward incompatible changes may be made. Backward compatible changes - may be added together with the corresponding interface version bump. - Backward incompatible changes are done by bumping the version number in - the protocol and interface names and resetting the interface version. - Once the protocol is to be declared stable, the 'z' prefix and the - version number in the protocol and interface names are removed and the - interface version number is reset. - - - - - This object is a manager which offers requests to start capturing from a - source. - - - - - Capture the next frame of an entire output. - - - - - - - - - Capture the next frame of an output's region. - - The region is given in output logical coordinates, see - xdg_output.logical_size. The region will be clipped to the output's - extents. - - - - - - - - - - - - - All objects created by the manager will still remain valid, until their - appropriate destroy request has been called. - - - - - - - This object represents a single frame. - - When created, a series of buffer events will be sent, each representing a - supported buffer type. The "buffer_done" event is sent afterwards to - indicate that all supported buffer types have been enumerated. The client - will then be able to send a "copy" request. If the capture is successful, - the compositor will send a "flags" event followed by a "ready" event. - - For objects version 2 or lower, wl_shm buffers are always supported, ie. - the "buffer" event is guaranteed to be sent. - - If the capture failed, the "failed" event is sent. This can happen anytime - before the "ready" event. - - Once either a "ready" or a "failed" event is received, the client should - destroy the frame. - - - - - Provides information about wl_shm buffer parameters that need to be - used for this frame. This event is sent once after the frame is created - if wl_shm buffers are supported. - - - - - - - - - - Copy the frame to the supplied buffer. The buffer must have the - correct size, see zwlr_screencopy_frame_v1.buffer and - zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a - supported format. - - If the frame is successfully copied, "flags" and "ready" events are - sent. Otherwise, a "failed" event is sent. - - - - - - - - - - - - - - - - Provides flags about the frame. This event is sent once before the - "ready" event. - - - - - - - Called as soon as the frame is copied, indicating it is available - for reading. This event includes the time at which the presentation took place. - - The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, - each component being an unsigned 32-bit value. Whole seconds are in - tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, - and the additional fractional part in tv_nsec as nanoseconds. Hence, - for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part - may have an arbitrary offset at start. - - After receiving this event, the client should destroy the object. - - - - - - - - - This event indicates that the attempted frame copy has failed. - - After receiving this event, the client should destroy the object. - - - - - - Destroys the frame. This request can be sent at any time by the client. - - - - - - - Same as copy, except it waits until there is damage to copy. - - - - - - - This event is sent right before the ready event when copy_with_damage is - requested. It may be generated multiple times for each copy_with_damage - request. - - The arguments describe a box around an area that has changed since the - last copy request that was derived from the current screencopy manager - instance. - - The union of all regions received between the call to copy_with_damage - and a ready event is the total damage since the prior ready event. - - - - - - - - - - - Provides information about linux-dmabuf buffer parameters that need to - be used for this frame. This event is sent once after the frame is - created if linux-dmabuf buffers are supported. - - - - - - - - - This event is sent once after all buffer events have been sent. - - The client should proceed to create a buffer of one of the supported - types, and send a "copy" request. - - - - diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 1b182434..3f50e190 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -18,26 +18,51 @@ namespace extern zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_v1_impl; } -static int create_anon_file(off_t size) +static void session_handle_buffer_size(void *data, + struct ext_image_copy_capture_session_v1*, + uint32_t width, uint32_t height) { - int fd = memfd_create("wf-live-preview", MFD_CLOEXEC); + TooltipMedia *toplevel = (TooltipMedia*)data; + toplevel->current_buffer_width = width; + toplevel->current_buffer_height = height; +} - if (fd == -1) - { - perror("memfd_create"); - return 1; - } +static void session_handle_shm_format(void *data, + struct ext_image_copy_capture_session_v1*, + uint32_t format) +{} - if (ftruncate(fd, size) == -1) - { - perror("ftruncate"); - close(fd); - return 1; - } +static void session_handle_dmabuf_device(void*, + struct ext_image_copy_capture_session_v1*, + struct wl_array*) +{} - return fd; +static void session_handle_dmabuf_format(void *data, + struct ext_image_copy_capture_session_v1*, + uint32_t format, + struct wl_array*) +{ + TooltipMedia *toplevel = (TooltipMedia*)data; + toplevel->current_buffer_format = format; } +static void session_handle_done(void *data, + struct ext_image_copy_capture_session_v1*) +{} + +static void session_handle_stopped(void*, + struct ext_image_copy_capture_session_v1 *session) +{} + +static const struct ext_image_copy_capture_session_v1_listener recording_session_listener = { + .buffer_size = session_handle_buffer_size, + .shm_format = session_handle_shm_format, + .dmabuf_device = session_handle_dmabuf_device, + .dmabuf_format = session_handle_dmabuf_format, + .done = session_handle_done, + .stopped = session_handle_stopped, +}; + #ifdef HAVE_DMABUF static void dmabuf_created(void *data, struct zwp_linux_buffer_params_v1*, struct wl_buffer *wl_buffer) @@ -45,7 +70,6 @@ static void dmabuf_created(void *data, struct zwp_linux_buffer_params_v1*, TooltipMedia *tooltip_media = (TooltipMedia*)data; tooltip_media->buffer = wl_buffer; - zwlr_screencopy_frame_v1_copy(tooltip_media->frame, tooltip_media->buffer); } static void dmabuf_failed(void *data, struct zwp_linux_buffer_params_v1*) @@ -63,289 +87,264 @@ static const struct zwp_linux_buffer_params_v1_listener params_listener = }; #endif // HAVE_DMABUF -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; - - if (tooltip_media->window_list->live_previews_dmabuf) - { - tooltip_media->shm_data = nullptr; - return; - } - - size_t size = width * height * int(stride / width); +/* Copy Capture Callbacks */ - if (tooltip_media->size != size) - { - if (tooltip_media->shm_data && tooltip_media->size) - { - if (munmap(tooltip_media->shm_data, tooltip_media->size) < 0) - { - perror("munmap failed"); - } - } - - tooltip_media->size = size; - auto anon_file = create_anon_file(size); - if (anon_file < 0) - { - perror("anon_file < 0"); - return; - } - - void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, anon_file, 0); - if (data == MAP_FAILED) - { - perror("data == MAP_FAILED"); - close(anon_file); - return; - } - - wl_shm_pool *pool = wl_shm_create_pool(tooltip_media->window_list->shm, anon_file, size); - tooltip_media->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); - wl_shm_pool_destroy(pool); - close(anon_file); - tooltip_media->buffer_width = width; - tooltip_media->buffer_height = height; - tooltip_media->buffer_stride = stride; - tooltip_media->shm_data = data; - } +static void frame_handle_transform(void*, + struct ext_image_copy_capture_frame_v1*, + uint32_t) +{} - zwlr_screencopy_frame_v1_copy(tooltip_media->frame, tooltip_media->buffer); -} +static void frame_handle_damage(void*, + struct ext_image_copy_capture_frame_v1*, + int32_t, int32_t, int32_t, int32_t) +{} -void handle_frame_flags(void*, - struct zwlr_screencopy_frame_v1*, - uint32_t) +static void frame_handle_presentation_time(void*, + struct ext_image_copy_capture_frame_v1*, + uint32_t, uint32_t, 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) +static void frame_handle_ready(void *data, + struct ext_image_copy_capture_frame_v1*) { TooltipMedia *tooltip_media = (TooltipMedia*)data; - tooltip_media->request_next_frame(); + tooltip_media->frame_in_flight = false; -#ifdef HAVE_DMABUF - if (tooltip_media->window_list->live_previews_dmabuf && tooltip_media->bo) + if (tooltip_media->buffer == nullptr) { - uint32_t stride = 0; - tooltip_media->map_data = nullptr; - tooltip_media->dmabuf_data = gbm_bo_map(tooltip_media->bo, - 0, 0, tooltip_media->buffer_width, tooltip_media->buffer_height, - GBM_BO_TRANSFER_READ, &stride, &tooltip_media->map_data); + printf("%s buffer null\n", __func__); - if (!tooltip_media->dmabuf_data) - { - perror("Failed to map bo"); - std::cerr << "Trying shm" << std::endl; - tooltip_media->window_list->live_previews_dmabuf = false; - return; - } - - if (tooltip_media->bo && tooltip_media->map_data) - { - gbm_bo_unmap(tooltip_media->bo, tooltip_media->map_data); - tooltip_media->map_data = nullptr; - } - - tooltip_media->buffer_stride = stride; - tooltip_media->size = tooltip_media->buffer_height * tooltip_media->buffer_stride; + return; } -#endif // HAVE_DMABUF - - if ((!tooltip_media->shm_data -#ifdef HAVE_DMABUF - && !tooltip_media->dmabuf_data -#endif // HAVE_DMABUF - ) || !tooltip_media->size) + uint32_t stride = 0; + void *map_data = NULL; + void *pixel_data = gbm_bo_map(tooltip_media->bo, 0, 0, tooltip_media->width, tooltip_media->height, + GBM_BO_TRANSFER_READ, &stride, &map_data); + if (!pixel_data) { + perror("failed to map bo"); return; } - std::shared_ptr bytes = 0; - size_t size = tooltip_media->buffer_height * tooltip_media->buffer_stride; - if (tooltip_media->shm_data) - { - bytes = Glib::Bytes::create(tooltip_media->shm_data, size); - } + /* Scale */ + auto pixbuf = Gdk::Pixbuf::create_from_data( + (const guint8*)pixel_data, + Gdk::Colorspace::RGB, + true, + 8, + tooltip_media->width, + tooltip_media->height, + stride); -#ifdef HAVE_DMABUF - else if (tooltip_media->dmabuf_data) - { - bytes = Glib::Bytes::create(tooltip_media->dmabuf_data, size); - tooltip_media->dmabuf_data = nullptr; - } -#endif // HAVE_DMABUF + auto w = 500; + auto h = tooltip_media->height * (500.0f / tooltip_media->width); + + auto scaled_pixbuf = pixbuf->scale_simple( + w, h, Gdk::InterpType::BILINEAR); + + /* Swap red and blue channels */ + size_t size = w * h * 4; + pixel_data = scaled_pixbuf->get_pixels(); + std::shared_ptr bytes = Glib::Bytes::create((unsigned char*)pixel_data, size); if (!bytes) { + gbm_bo_unmap(tooltip_media->bo, map_data); return; } 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); + builder->set_width(w); + builder->set_height(h); + builder->set_stride(w * 4); + builder->set_format(Gdk::MemoryFormat::B8G8R8A8); auto texture = builder->build(); tooltip_media->set_paintable(texture); + + gbm_bo_unmap(tooltip_media->bo, map_data); } -void handle_frame_failed(void*, struct zwlr_screencopy_frame_v1*) -{} +static void frame_handle_failed(void *data, + struct ext_image_copy_capture_frame_v1 *handle, + uint32_t reason) +{ + TooltipMedia *tooltip_media = (TooltipMedia*)data; + std::cerr << "Failed to copy frame because reason: " << reason << std::endl; + ext_image_copy_capture_frame_v1_destroy(handle); + tooltip_media->frame = nullptr; + tooltip_media->frame_in_flight = false; +} -void handle_frame_damage(void*, - struct zwlr_screencopy_frame_v1*, - uint32_t, - uint32_t, - uint32_t, - uint32_t) -{} +static const struct ext_image_copy_capture_frame_v1_listener frame_listener = { + .transform = frame_handle_transform, + .damage = frame_handle_damage, + .presentation_time = frame_handle_presentation_time, + .ready = frame_handle_ready, + .failed = frame_handle_failed, +}; -void handle_frame_linux_dmabuf(void *data, - struct zwlr_screencopy_frame_v1 *frame, - uint32_t format, - uint32_t width, - uint32_t height) +static void frame_handle_linux_dmabuf(uint32_t width, uint32_t height, TooltipMedia *tooltip_media) { -#ifdef HAVE_DMABUF - TooltipMedia *tooltip_media = (TooltipMedia*)data; + auto format = (tooltip_media->current_buffer_format == WL_SHM_FORMAT_XRGB8888) ? + GBM_FORMAT_XRGB8888 : GBM_FORMAT_ARGB8888; - if (!tooltip_media->window_list->live_previews_dmabuf) + if (tooltip_media->bo) { - return; + gbm_bo_destroy(tooltip_media->bo); + tooltip_media->bo = nullptr; } - tooltip_media->buffer_width = width; - tooltip_media->buffer_height = height; - - if (!tooltip_media->buffer) + if (tooltip_media->params) { - if (tooltip_media->bo) - { - if (tooltip_media->buffer) - { - wl_buffer_destroy(tooltip_media->buffer); - tooltip_media->buffer = nullptr; - } + zwp_linux_buffer_params_v1_destroy(tooltip_media->params); + tooltip_media->params = nullptr; + } - if (tooltip_media->params) - { - zwp_linux_buffer_params_v1_destroy(tooltip_media->params); - tooltip_media->params = nullptr; - } + auto w = width; + auto h = height; - if (tooltip_media->bo) - { - gbm_bo_destroy(tooltip_media->bo); - tooltip_media->bo = nullptr; - } - } + const uint64_t modifier = 0; // DRM_FORMAT_MOD_LINEAR + tooltip_media->bo = gbm_bo_create_with_modifiers(tooltip_media->window_list->dmabuf_device, w, h, + format, &modifier, 1); + if (tooltip_media->bo == NULL) + { + tooltip_media->bo = gbm_bo_create(tooltip_media->window_list->dmabuf_device, w, h, + format, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING); + } - const uint64_t modifier = 0; // DRM_FORMAT_MOD_LINEAR - tooltip_media->bo = gbm_bo_create_with_modifiers(tooltip_media->window_list->dmabuf_device, - tooltip_media->buffer_width, - tooltip_media->buffer_height, format, &modifier, 1); - if (!tooltip_media->bo) - { - tooltip_media->bo = gbm_bo_create(tooltip_media->window_list->dmabuf_device, - tooltip_media->buffer_width, - tooltip_media->buffer_height, format, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING); - } + if (tooltip_media->bo == NULL) + { + perror("failed to create gbm bo"); + return; + } - if (!tooltip_media->bo) - { - perror("Failed to create gbm bo"); - std::cerr << "Trying shm" << std::endl; - tooltip_media->window_list->live_previews_dmabuf = false; - return; - } + tooltip_media->width = gbm_bo_get_width(tooltip_media->bo); + tooltip_media->height = gbm_bo_get_height(tooltip_media->bo); + tooltip_media->stride = gbm_bo_get_stride(tooltip_media->bo); + tooltip_media->params = zwp_linux_dmabuf_v1_create_params(tooltip_media->window_list->dmabuf); - tooltip_media->buffer_stride = gbm_bo_get_stride(tooltip_media->bo); + uint64_t mod = gbm_bo_get_modifier(tooltip_media->bo); + zwp_linux_buffer_params_v1_add(tooltip_media->params, + gbm_bo_get_fd(tooltip_media->bo), 0, + gbm_bo_get_offset(tooltip_media->bo, 0), + gbm_bo_get_stride(tooltip_media->bo), + mod >> 32, mod & 0xffffffff); - tooltip_media->params = zwp_linux_dmabuf_v1_create_params(tooltip_media->window_list->dmabuf); + zwp_linux_buffer_params_v1_add_listener(tooltip_media->params, ¶ms_listener, tooltip_media); + zwp_linux_buffer_params_v1_create(tooltip_media->params, w, h, format, 0); +} - uint64_t mod = gbm_bo_get_modifier(tooltip_media->bo); - zwp_linux_buffer_params_v1_add(tooltip_media->params, - gbm_bo_get_fd(tooltip_media->bo), 0, - gbm_bo_get_offset(tooltip_media->bo, 0), - gbm_bo_get_stride(tooltip_media->bo), - mod >> 32, mod & 0xffffffff); +void TooltipMedia::request_next_frame() +{ + if (this->frame) + { + ext_image_copy_capture_frame_v1_destroy(this->frame); + this->frame = nullptr; + } - zwp_linux_buffer_params_v1_add_listener(tooltip_media->params, ¶ms_listener, tooltip_media); - zwp_linux_buffer_params_v1_create(tooltip_media->params, tooltip_media->buffer_width, - tooltip_media->buffer_height, format, 0); - } else + if ((current_buffer_width <= 0) || (current_buffer_height <= 0)) { - zwlr_screencopy_frame_v1_copy(frame, tooltip_media->buffer); + printf("%s invalid size\n", __func__); + return; } -#endif // HAVE_DMABUF -} + if (!recording_session) + { + return; + } -void handle_frame_buffer_done(void*, struct zwlr_screencopy_frame_v1*) -{} + bool dirty = (width != current_buffer_width) || (height != current_buffer_height) || + !buffer; -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, -}; + width = current_buffer_width; + height = current_buffer_height; -bool TooltipMedia::request_next_frame() -{ - if (this->frame) + if (dirty) { - zwlr_screencopy_frame_v1_destroy(this->frame); - this->frame = nullptr; + frame_handle_linux_dmabuf(width, height, this); } - if (!WayfireShellApp::get().live_preview_output) + if (!buffer) { - return false; + return; + } + + if (frame_in_flight) + { + return; + } + + if (frame) + { + ext_image_copy_capture_frame_v1_destroy(frame); + frame = NULL; } - this->frame = zwlr_screencopy_manager_v1_capture_output(this->window_list->screencopy_manager, 0, - WayfireShellApp::get().live_preview_output); - zwlr_screencopy_frame_v1_add_listener(this->frame, &screencopy_frame_listener, this); + frame = ext_image_copy_capture_session_v1_create_frame(recording_session); + frame = frame; + + ext_image_copy_capture_frame_v1_add_listener(frame, &frame_listener, this); + ext_image_copy_capture_frame_v1_attach_buffer(frame, buffer); + ext_image_copy_capture_frame_v1_damage_buffer(frame, 0, 0, width, height); + ext_image_copy_capture_frame_v1_capture(frame); + frame_in_flight = true; +} - return true; +void TooltipMedia::start_toplevel_source_session() +{ + copy_capture_source = ext_foreign_toplevel_image_capture_source_manager_v1_create_source( + this->window_list->toplevel_capture_manager, + this->ext_handle); + recording_session = ext_image_copy_capture_manager_v1_create_session( + this->window_list->copy_capture_manager, + copy_capture_source, 0); + ext_image_copy_capture_session_v1_add_listener(recording_session, &recording_session_listener, this); } -TooltipMedia::TooltipMedia(WayfireWindowList *window_list) +TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_handle_v1 *ext_handle) { this->window_list = window_list; + this->ext_handle = ext_handle; this->shm = window_list->shm; + set_can_shrink(true); + set_content_fit(Gtk::ContentFit::CONTAIN); + set_vexpand(false); + set_hexpand(false); + set_size_request(200, -1); + + start_toplevel_source_session(); - this->add_tick_callback([=] (const Glib::RefPtr& clock) + Glib::signal_timeout().connect( + [this] () { - return !this->request_next_frame(); - }); + this->request_next_frame(); + + return timer_continue; + }, 33); } TooltipMedia::~TooltipMedia() { - if (this->frame) + timer_continue = false; + + if (frame) + { + ext_image_copy_capture_frame_v1_destroy(frame); + } + + if (copy_capture_source) + { + ext_image_capture_source_v1_destroy(copy_capture_source); + } + + if (recording_session) { - zwlr_screencopy_frame_v1_destroy(this->frame); + ext_image_copy_capture_session_v1_destroy(recording_session); } if ( @@ -387,6 +386,7 @@ TooltipMedia::~TooltipMedia() class WayfireToplevel::impl { zwlr_foreign_toplevel_handle_v1 *handle, *parent; + ext_foreign_toplevel_handle_v1 *ext_handle; std::vector children; uint32_t state; uint64_t view_id; @@ -511,56 +511,13 @@ class WayfireToplevel::impl auto motion_controller = Gtk::EventControllerMotion::create(); button_leave_signal = motion_controller->signal_leave().connect([=] () { - wf::json_t live_window_release_output_request; - live_window_release_output_request["method"] = "live_previews/release_output"; - this->window_list->ipc_client->send(live_window_release_output_request.serialize(), - [=] (wf::json_t data) - { - unset_tooltip_media(); - this->window_list->live_window_preview_view_id = 0; - if (data.serialize().find("error") != std::string::npos) - { - if (this->window_list->live_window_preview_tooltips) - { - std::cerr << - "Error releasing output for live preview stream! (is live-previews plugin disabled?)" - << - std::endl; - } - - this->window_list->enable_normal_tooltips_flag(true); - return; - } - }); + unset_tooltip_media(); }); button.add_controller(motion_controller); motion_controller = Gtk::EventControllerMotion::create(); signals.push_back(motion_controller->signal_enter().connect([=] (double x, double y) { - wf::json_t live_window_preview_stream_request; - live_window_preview_stream_request["method"] = "live_previews/request_stream"; - wf::json_t view_id_int; - view_id_int["id"] = this->view_id; - live_window_preview_stream_request["data"] = view_id_int; - this->window_list->ipc_client->send(live_window_preview_stream_request.serialize(), - [=] (wf::json_t data) - { - if ((data.serialize().find("error") != std::string::npos) && - this->window_list->live_window_preview_tooltips) - { - std::cerr << data.serialize() << std::endl; - std::cerr << - "Error acquiring live preview stream. (is live-previews wayfire plugin enabled?)" << - std::endl; - this->window_list->enable_normal_tooltips_flag(true); - button.set_tooltip_text(title); - - return; - } - - set_tooltip_media(); - update_tooltip(); - }); + set_tooltip_media(); })); button.add_controller(motion_controller); button.set_tooltip_text("none"); @@ -572,7 +529,6 @@ class WayfireToplevel::impl return query_tooltip(x, y, keyboard_mode, tooltip); }, false)); button.set_has_tooltip(true); - update_tooltip(); send_rectangle_hints(); set_state(0); // will set the appropriate button style @@ -585,7 +541,7 @@ class WayfireToplevel::impl return; } - this->tooltip_media = Gtk::make_managed(this->window_list); + this->tooltip_media = Gtk::make_managed(this->window_list, this->ext_handle); this->custom_tooltip_content.append(*this->tooltip_media); } @@ -600,68 +556,9 @@ class WayfireToplevel::impl this->tooltip_media = nullptr; } - void update_tooltip() + void set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle) { - wf::json_t ipc_methods_request; - ipc_methods_request["method"] = "list-methods"; - this->window_list->ipc_client->send(ipc_methods_request.serialize(), [=] (wf::json_t data) - { - if (data.serialize().find("error") != std::string::npos) - { - std::cerr << "Error getting ipc methods list!" << std::endl; - this->window_list->enable_normal_tooltips_flag(true); - return; - } - - if ((data.serialize().find("live_previews/request_stream") == std::string::npos) || - (data.serialize().find("live_previews/release_output") == std::string::npos)) - { - unset_tooltip_media(); - if (this->window_list->live_window_preview_tooltips) - { - if (this->window_list->live_window_previews_opt) - { - std::cout << "wf-shell configuration [panel] option 'live_window_previews' is set to 'true' but live-previews wayfire plugin is disabled." << std::endl; - this->window_list->enable_normal_tooltips_flag(true); - } - - for (const auto& toplevel_button : this->window_list->toplevels) - { - if (toplevel_button.second && toplevel_button.second->pimpl) - { - toplevel_button.second->unset_tooltip_media(); - } - } - } - } else - { - set_tooltip_media(); - if (!this->window_list->live_window_preview_tooltips) - { - if (!this->window_list->live_window_previews_opt) - { - std::cout << "Detected live-previews plugin is enabled but wf-shell configuration [panel] option 'live_window_previews' is set to 'false'." << std::endl; - } else - { - std::cout << "Enabling live window preview tooltips." << std::endl; - } - - this->window_list->enable_normal_tooltips_flag(false); - for (const auto& toplevel_button : this->window_list->toplevels) - { - if (toplevel_button.second && toplevel_button.second->pimpl) - { - toplevel_button.second->set_tooltip_media(); - } - } - } - } - }); - if (!this->window_list->live_window_previews_enabled()) - { - this->window_list->normal_title_tooltips = true; - button.set_tooltip_text(title); - } + this->ext_handle = handle; } int grab_off_x; @@ -895,6 +792,11 @@ class WayfireToplevel::impl } } + std::string get_app_id() + { + return this->app_id; + } + void send_rectangle_hints() { for (const auto& toplevel_button : window_list->toplevels) @@ -1104,6 +1006,11 @@ void WayfireToplevel::unset_tooltip_media() pimpl->unset_tooltip_media(); } +void WayfireToplevel::set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle) +{ + pimpl->set_list_toplevel_handle(handle); +} + /* wl_array_for_each isn't supported in C++, so we have to manually * get the data from wl_array, see: * @@ -1144,7 +1051,21 @@ static void handle_toplevel_state(void *data, toplevel_t, wl_array *state) } static void handle_toplevel_done(void *data, toplevel_t) -{} +{ + auto impl = static_cast(data); + auto window_list = impl->window_list; + + auto wf_id = impl->get_view_id_from_full_app_id(impl->get_app_id()); + for (auto & list_toplevel : window_list->list_toplevels) + { + auto id = impl->get_view_id_from_full_app_id(list_toplevel.second->app_id); + if (wf_id == id) + { + impl->set_list_toplevel_handle(list_toplevel.first); + break; + } + } +} static void remove_child_from_parent(WayfireToplevel::impl *impl, toplevel_t child) { diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index fc544a33..5e2d2ae9 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -6,7 +6,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include "wf-shell-app.hpp" @@ -35,23 +37,30 @@ class TooltipMedia : public Gtk::Picture wl_shm *shm = nullptr; wl_buffer *buffer = nullptr; void *shm_data = nullptr; - zwlr_screencopy_frame_v1 *frame = nullptr; - uint32_t buffer_width; - uint32_t buffer_height; - uint32_t buffer_stride; + ext_foreign_toplevel_handle_v1 *ext_handle = NULL; + ext_image_copy_capture_frame_v1 *frame = NULL; + ext_image_capture_source_v1 *copy_capture_source = NULL; + ext_image_copy_capture_session_v1 *recording_session = NULL; + bool frame_in_flight = false; + bool timer_continue = true; + uint32_t current_buffer_format = GBM_FORMAT_ARGB8888; + uint32_t current_buffer_width = 0, width = -1; + uint32_t current_buffer_height = 0, height = -1; + uint32_t stride; size_t size = 0; #ifdef HAVE_DMABUF gbm_bo *bo = nullptr; zwp_linux_buffer_params_v1 *params = nullptr; void *dmabuf_data = nullptr; - void *map_data = nullptr; + void *map_data = nullptr; #endif // HAVE_DMABUF - TooltipMedia(WayfireWindowList *window_list); + TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_handle_v1 *ext_handle); ~TooltipMedia(); - bool request_next_frame(); + void start_toplevel_source_session(); + void request_next_frame(); }; /* Represents a single opened toplevel window. @@ -68,6 +77,7 @@ class WayfireToplevel void set_hide_text(bool hide_text); void set_tooltip_media(); void unset_tooltip_media(); + void set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle); class impl; diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 3fa0b958..e8b26e05 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -28,6 +28,75 @@ zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_v1_impl = { .finished = handle_manager_finished, }; +/* Toplevel Callbacks */ + +static void toplevel_handle_closed(void *data, + struct ext_foreign_toplevel_handle_v1 *handle) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + ext_foreign_toplevel_handle_v1_destroy(handle); + window_list->list_toplevels.erase(handle); +} + +static void toplevel_handle_done(void *data, + struct ext_foreign_toplevel_handle_v1 *handle) +{} + +static void toplevel_handle_title(void *data, + struct ext_foreign_toplevel_handle_v1 *handle, + const char *title) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + window_list->list_toplevels[handle]->title = title; +} + +static void toplevel_handle_app_id(void *data, + struct ext_foreign_toplevel_handle_v1 *handle, + const char *app_id) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + window_list->list_toplevels[handle]->app_id = app_id; +} + +static void toplevel_handle_identifier(void *data, + struct ext_foreign_toplevel_handle_v1 *handle, + const char *identifier) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + window_list->list_toplevels[handle]->identifier = identifier; +} + +ext_foreign_toplevel_handle_v1_listener toplevels_listener = +{ + .closed = toplevel_handle_closed, + .done = toplevel_handle_done, + .title = toplevel_handle_title, + .app_id = toplevel_handle_app_id, + .identifier = toplevel_handle_identifier, +}; + +/* Static callbacks for toplevel list object */ +static void handle_toplevel(void *data, + struct ext_foreign_toplevel_list_v1 *list, + struct ext_foreign_toplevel_handle_v1 *handle) +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + window_list->list_toplevels[handle] = std::make_unique(); + ext_foreign_toplevel_handle_v1_add_listener(handle, &toplevels_listener, window_list); +} + +static void handle_finished(void *data, + struct ext_foreign_toplevel_list_v1 *list) +{ + ext_foreign_toplevel_list_v1_stop(list); + ext_foreign_toplevel_list_v1_destroy(list); +} + +ext_foreign_toplevel_list_v1_listener toplevel_list_v1_impl = { + .toplevel = handle_toplevel, + .finished = handle_finished, +}; + #ifdef HAVE_DMABUF static void dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *feedback) { @@ -138,12 +207,27 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name 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) + } else if (strcmp(interface, ext_foreign_toplevel_list_v1_interface.name) == 0) { - window_list->screencopy_manager = (zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, - &zwlr_screencopy_manager_v1_interface, + auto foreign_toplevel_list = (ext_foreign_toplevel_list_v1*) + wl_registry_bind(registry, name, + &ext_foreign_toplevel_list_v1_interface, version); + window_list->foreign_toplevel_list = foreign_toplevel_list; + ext_foreign_toplevel_list_v1_add_listener(foreign_toplevel_list, + &toplevel_list_v1_impl, window_list); + } else if (strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) == 0) + { + auto copy_capture_manager = (ext_image_copy_capture_manager_v1*)wl_registry_bind(registry, name, + &ext_image_copy_capture_manager_v1_interface, version); + window_list->copy_capture_manager = copy_capture_manager; + } else if (strcmp(interface, ext_foreign_toplevel_image_capture_source_manager_v1_interface.name) == 0) + { + auto toplevel_capture_manager = + (ext_foreign_toplevel_image_capture_source_manager_v1*)wl_registry_bind(registry, name, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, version); + window_list->toplevel_capture_manager = toplevel_capture_manager; + printf("set toplevel_capture_manager: %p\n", toplevel_capture_manager); } #ifdef HAVE_DMABUF @@ -257,12 +341,11 @@ void WayfireWindowList::init(Gtk::Box *container) wl_registry_destroy(registry); - if (!this->manager) + if (!this->copy_capture_manager) { std::cerr << "Compositor doesn't support" << - " wlr-foreign-toplevel-management." << + " ext-image-copy-capture." << "The window-list widget will not be initialized." << std::endl; - wl_registry_destroy(registry); return; } @@ -386,14 +469,14 @@ void WayfireWindowList::handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 this->manager = manager; } -void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle) +void WayfireWindowList::handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *toplevel) { - toplevels[handle] = std::unique_ptr(new WayfireToplevel(this, handle)); + toplevels[toplevel] = std::make_unique(this, toplevel); } -void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle) +void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *toplevel) { - toplevels.erase(handle); + toplevels.erase(toplevel); } void WayfireWindowList::on_event(wf::json_t data) @@ -420,7 +503,6 @@ WayfireWindowList::~WayfireWindowList() wl_shm_destroy(this->shm); zwlr_foreign_toplevel_manager_v1_destroy(this->manager); - zwlr_screencopy_manager_v1_destroy(this->screencopy_manager); #ifdef HAVE_DMABUF if (this->dmabuf) { diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 5958bc34..93989ac0 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -11,6 +11,13 @@ #include #endif // HAVE_DMABUF +struct WayfireListToplevel +{ + std::string title; + std::string app_id; + std::string identifier; +}; + class WayfireToplevel; class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubscriber @@ -21,11 +28,15 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs public: std::map> toplevels; + std::map> list_toplevels; wl_display *display; wl_shm *shm = nullptr; - zwlr_foreign_toplevel_manager_v1 *manager = nullptr; - zwlr_screencopy_manager_v1 *screencopy_manager = nullptr; + zwlr_foreign_toplevel_manager_v1 *manager = NULL; + ext_foreign_toplevel_list_v1 *foreign_toplevel_list = NULL; + ext_image_copy_capture_manager_v1 *copy_capture_manager = NULL; + ext_foreign_toplevel_image_capture_source_manager_v1 *toplevel_capture_manager = NULL; WayfireOutput *output; Gtk::ScrolledWindow scrolled_window; @@ -33,8 +44,8 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs virtual ~WayfireWindowList(); void handle_toplevel_manager(zwlr_foreign_toplevel_manager_v1 *manager); + void handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *toplevel); void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); - void handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *handle); wayfire_config *get_config(); From 74bf5cac3aa1f2f9581be78e7bea08ef36c01224 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 14:23:13 -0600 Subject: [PATCH 02/10] window-list: Make stopping live-preview timer more reliable --- src/panel/widgets/window-list/toplevel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 3f50e190..5da5b216 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -322,9 +322,14 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_ Glib::signal_timeout().connect( [this] () { + if (!timer_continue) + { + return false; + } + this->request_next_frame(); - return timer_continue; + return true; }, 33); } From 37d7e1303908d0155646e664fd1d1bd05259081a Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 14:27:47 -0600 Subject: [PATCH 03/10] window-list: Finish removing dependence on wayfire ipc --- src/panel/widgets/window-list/toplevel.cpp | 19 ----- src/panel/widgets/window-list/window-list.cpp | 74 ------------------- src/panel/widgets/window-list/window-list.hpp | 1 - 3 files changed, 94 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 5da5b216..b818160c 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -734,21 +734,6 @@ class WayfireToplevel::impl return false; } - if (!this->window_list->live_window_previews_enabled()) - { - if (!this->window_list->normal_title_tooltips) - { - std::cerr << - "Normal title tooltips enabled. To enable live window preview tooltips, make sure to set [panel] option 'live_window_previews = true' in wf-shell configuration and enable wayfire plugin live-previews" - << - std::endl; - this->window_list->enable_normal_tooltips_flag(true); - } - - tooltip->set_text(title); - return true; - } - this->window_list->live_window_preview_view_id = this->view_id; tooltip->set_custom(this->custom_tooltip_content); @@ -830,10 +815,6 @@ class WayfireToplevel::impl void set_title(std::string title) { this->title = title; - if (!this->window_list->live_window_previews_enabled()) - { - button.set_tooltip_text(title); - } label.set_text(title); } diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index e8b26e05..117a61b8 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -254,82 +254,8 @@ static struct wl_registry_listener registry_listener = ®istry_remove_object }; -void WayfireWindowList::live_window_previews_plugin_check() -{ - wf::json_t ipc_methods_request; - ipc_methods_request["method"] = "list-methods"; - this->ipc_client->send(ipc_methods_request.serialize(), [=] (wf::json_t data) - { - if (data.serialize().find( - "error") != std::string::npos) - { - std::cerr << "Error getting ipc methods list! (are ipc and ipc-rules plugins loaded?)" << std::endl; - this->enable_normal_tooltips_flag(true); - return; - } - - if ((data.serialize().find("live_previews/request_stream") == std::string::npos) || - (data.serialize().find( - "live_previews/release_output") == std::string::npos)) - { - std::cerr << "Did not find live-previews ipc methods in methods list. Disabling live window preview tooltips. (is the live-previews plugin enabled?)" << std::endl; - this->enable_normal_tooltips_flag( - true); - } else - { - if (!this->live_window_previews_opt) - { - std::cout << "Detected live-previews plugin is enabled but wf-shell configuration [panel] option 'live_window_previews' is set to 'false'." << std::endl; - this->enable_normal_tooltips_flag( - true); - } else - { - std::cout << "Enabling live window preview tooltips using " << - std::string(live_previews_dmabuf ? "dmabuf" : "shm") << - " transfers on output " << this->output->monitor->get_connector() << - std::endl; - this->enable_normal_tooltips_flag(false); - } - } - }); -} - -void WayfireWindowList::enable_ipc(bool enable) -{ - if (!this->ipc_client) - { - this->ipc_client = WayfirePanelApp::get().get_ipc_server_instance()->create_client(); - } - - if (!this->ipc_client) - { - std::cerr << - "Failed to connect to ipc. Live window previews will not be available. (are ipc and ipc-rules plugins loaded?)"; - } -} - -void WayfireWindowList::enable_normal_tooltips_flag(bool enable) -{ - this->normal_title_tooltips = enable; - this->live_window_preview_tooltips = !enable; -} - -bool WayfireWindowList::live_window_previews_enabled() -{ - return this->live_window_previews_opt && this->live_window_preview_tooltips && this->ipc_client; -} - void WayfireWindowList::init(Gtk::Box *container) { - enable_ipc(this->live_window_previews_opt); - live_window_previews_plugin_check(); - - this->live_window_previews_opt.set_callback([=] () - { - enable_ipc(this->live_window_previews_opt); - live_window_previews_plugin_check(); - }); - auto gdk_display = gdk_display_get_default(); auto display = gdk_wayland_display_get_wl_display(gdk_display); diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 93989ac0..b9c0e02e 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -5,7 +5,6 @@ #include "../../widget.hpp" #include "toplevel.hpp" #include "layout.hpp" -#include "wf-ipc.hpp" #ifdef HAVE_DMABUF #include From 681bded0929a6357bb29c2d6bf21f1515c7637b1 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 15:35:23 -0600 Subject: [PATCH 04/10] window-list: Disconnect frame timer on tooltip destroy --- src/panel/widgets/window-list/toplevel.cpp | 3 ++- src/panel/widgets/window-list/toplevel.hpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index b818160c..4135999a 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -319,7 +319,7 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_ start_toplevel_source_session(); - Glib::signal_timeout().connect( + timer_connection = Glib::signal_timeout().connect( [this] () { if (!timer_continue) @@ -335,6 +335,7 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_ TooltipMedia::~TooltipMedia() { + timer_connection.disconnect(); timer_continue = false; if (frame) diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 5e2d2ae9..79bc939a 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -41,6 +41,7 @@ class TooltipMedia : public Gtk::Picture ext_image_copy_capture_frame_v1 *frame = NULL; ext_image_capture_source_v1 *copy_capture_source = NULL; ext_image_copy_capture_session_v1 *recording_session = NULL; + sigc::connection timer_connection; bool frame_in_flight = false; bool timer_continue = true; uint32_t current_buffer_format = GBM_FORMAT_ARGB8888; From 0e896d180aa0cb0df9afaac429d9f130d28e59e1 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 17:17:15 -0600 Subject: [PATCH 05/10] window-list: Don't destroy frame before it's finished --- src/panel/widgets/window-list/toplevel.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 4135999a..51e74724 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -241,12 +241,6 @@ static void frame_handle_linux_dmabuf(uint32_t width, uint32_t height, TooltipMe void TooltipMedia::request_next_frame() { - if (this->frame) - { - ext_image_copy_capture_frame_v1_destroy(this->frame); - this->frame = nullptr; - } - if ((current_buffer_width <= 0) || (current_buffer_height <= 0)) { printf("%s invalid size\n", __func__); From 8f759dbbb20d2cc90957aba48d4b1208ef165c93 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 18:45:58 -0600 Subject: [PATCH 06/10] window-list: Choose normal text or live preview tooltips depending on plugin state --- src/panel/widgets/window-list/toplevel.cpp | 66 ++++++++---------- src/panel/widgets/window-list/toplevel.hpp | 4 +- src/panel/widgets/window-list/window-list.cpp | 69 ++++++++++++++++++- src/panel/widgets/window-list/window-list.hpp | 5 +- 4 files changed, 102 insertions(+), 42 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 51e74724..bfcbe445 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -386,7 +386,7 @@ TooltipMedia::~TooltipMedia() class WayfireToplevel::impl { zwlr_foreign_toplevel_handle_v1 *handle, *parent; - ext_foreign_toplevel_handle_v1 *ext_handle; + ext_foreign_toplevel_handle_v1 *ext_handle = NULL; std::vector children; uint32_t state; uint64_t view_id; @@ -536,7 +536,7 @@ class WayfireToplevel::impl void set_tooltip_media() { - if (this->tooltip_media) + if (this->tooltip_media || !this->ext_handle) { return; } @@ -556,7 +556,12 @@ class WayfireToplevel::impl this->tooltip_media = nullptr; } - void set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle) + ext_foreign_toplevel_handle_v1 *get_ext_handle() + { + return this->ext_handle; + } + + void set_ext_handle(ext_foreign_toplevel_handle_v1 *handle) { this->ext_handle = handle; } @@ -729,45 +734,22 @@ class WayfireToplevel::impl return false; } - this->window_list->live_window_preview_view_id = this->view_id; + if (this->window_list->list_toplevels.empty()) + { + tooltip->set_text(title); + } + tooltip->set_custom(this->custom_tooltip_content); return true; } - uint64_t get_view_id_from_full_app_id(const std::string& app_id) - { - const std::string sub_str = "wf-ipc-"; - size_t pos = app_id.find(sub_str); - - if (pos != std::string::npos) - { - size_t suffix_start_index = pos + sub_str.length(); - if (suffix_start_index < app_id.length()) - { - try { - uint64_t view_id = std::stoi(app_id.substr(suffix_start_index, std::string::npos)); - return view_id; - } catch (...) - { - return 0; - } - } else - { - return 0; - } - } else - { - return 0; - } - } - void set_app_id(std::string app_id) { WfOption minimal_panel_height{"panel/minimal_height"}; this->app_id = app_id; IconProvider::image_set_icon(image, app_id); - this->view_id = get_view_id_from_full_app_id(app_id); + this->view_id = this->window_list->get_view_id_from_full_app_id(app_id); if (this->view_id == 0) { std::cerr << "Failed to get view id from app_id. " << @@ -987,9 +969,19 @@ void WayfireToplevel::unset_tooltip_media() pimpl->unset_tooltip_media(); } -void WayfireToplevel::set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle) +ext_foreign_toplevel_handle_v1*WayfireToplevel::get_ext_handle() +{ + return pimpl->get_ext_handle(); +} + +void WayfireToplevel::set_ext_handle(ext_foreign_toplevel_handle_v1 *handle) +{ + pimpl->set_ext_handle(handle); +} + +std::string WayfireToplevel::get_app_id() { - pimpl->set_list_toplevel_handle(handle); + return pimpl->get_app_id(); } /* wl_array_for_each isn't supported in C++, so we have to manually @@ -1036,13 +1028,13 @@ static void handle_toplevel_done(void *data, toplevel_t) auto impl = static_cast(data); auto window_list = impl->window_list; - auto wf_id = impl->get_view_id_from_full_app_id(impl->get_app_id()); + auto wf_id = window_list->get_view_id_from_full_app_id(impl->get_app_id()); for (auto & list_toplevel : window_list->list_toplevels) { - auto id = impl->get_view_id_from_full_app_id(list_toplevel.second->app_id); + auto id = window_list->get_view_id_from_full_app_id(list_toplevel.second->app_id); if (wf_id == id) { - impl->set_list_toplevel_handle(list_toplevel.first); + impl->set_ext_handle(list_toplevel.first); break; } } diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 79bc939a..5ee9a6c1 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -72,13 +72,15 @@ class WayfireToplevel WayfireToplevel(WayfireWindowList *window_list, zwlr_foreign_toplevel_handle_v1 *handle); uint32_t get_state(); + std::string get_app_id(); void send_rectangle_hint(); std::vector& get_children(); ~WayfireToplevel(); void set_hide_text(bool hide_text); void set_tooltip_media(); void unset_tooltip_media(); - void set_list_toplevel_handle(ext_foreign_toplevel_handle_v1 *handle); + void set_ext_handle(ext_foreign_toplevel_handle_v1 *handle); + ext_foreign_toplevel_handle_v1 *get_ext_handle(); class impl; diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 117a61b8..e22803c3 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -34,13 +34,53 @@ static void toplevel_handle_closed(void *data, struct ext_foreign_toplevel_handle_v1 *handle) { WayfireWindowList *window_list = (WayfireWindowList*)data; + for (auto & toplevel : window_list->toplevels) + { + if (!toplevel.second) + { + continue; + } + + if (toplevel.second->get_ext_handle() == handle) + { + toplevel.second->set_ext_handle(NULL); + break; + } + } + ext_foreign_toplevel_handle_v1_destroy(handle); window_list->list_toplevels.erase(handle); } static void toplevel_handle_done(void *data, struct ext_foreign_toplevel_handle_v1 *handle) -{} +{ + WayfireWindowList *window_list = (WayfireWindowList*)data; + + for (auto & toplevel : window_list->toplevels) + { + if (!toplevel.second) + { + continue; + } + + auto wf_id = window_list->get_view_id_from_full_app_id(toplevel.second->get_app_id()); + for (auto & list_toplevel : window_list->list_toplevels) + { + if (!list_toplevel.second) + { + continue; + } + + auto id = window_list->get_view_id_from_full_app_id(list_toplevel.second->app_id); + if (wf_id == id) + { + toplevel.second->set_ext_handle(list_toplevel.first); + break; + } + } + } +} static void toplevel_handle_title(void *data, struct ext_foreign_toplevel_handle_v1 *handle, @@ -254,6 +294,33 @@ static struct wl_registry_listener registry_listener = ®istry_remove_object }; +uint64_t WayfireWindowList::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 WayfireWindowList::init(Gtk::Box *container) { auto gdk_display = gdk_display_get_default(); diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index b9c0e02e..85a1f7a4 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -46,6 +46,8 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs void handle_new_toplevel(zwlr_foreign_toplevel_handle_v1 *toplevel); void handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 *handle); + uint64_t get_view_id_from_full_app_id(const std::string& app_id); + wayfire_config *get_config(); void init(Gtk::Box *container) override; @@ -86,13 +88,10 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs void handle_new_wl_output(wl_output *output); void on_event(wf::json_t data) override; std::shared_ptr ipc_client; - bool live_window_preview_tooltips = false; bool normal_title_tooltips = false; void enable_normal_tooltips_flag(bool enable); uint64_t live_window_preview_view_id = 0; - void live_window_previews_plugin_check(); void enable_ipc(bool enable); - bool live_window_previews_enabled(); bool live_previews_dmabuf = true; #ifdef HAVE_DMABUF From 597ff2f04bb5d13aa1f2415af5eb3ecddacfca39 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 19:57:48 -0600 Subject: [PATCH 07/10] window-list: shm: Drop. --- meson.build | 8 ++-- meson_options.txt | 6 --- src/panel/meson.build | 4 +- src/panel/widgets/window-list/toplevel.cpp | 38 +++++-------------- src/panel/widgets/window-list/toplevel.hpp | 14 ++----- src/panel/widgets/window-list/window-list.cpp | 34 ++--------------- src/panel/widgets/window-list/window-list.hpp | 14 +------ 7 files changed, 25 insertions(+), 93 deletions(-) diff --git a/meson.build b/meson.build index da74da51..eda15dcf 100644 --- a/meson.build +++ b/meson.build @@ -33,8 +33,8 @@ dbusmenu_gtk = dependency('dbusmenu-glib-0.4') xkbregistry = dependency('xkbregistry') json = subproject('wf-json').get_variable('wfjson') openssl = dependency('openssl') -gbm = dependency('gbm', required: get_option('live-previews-dmabuf')) -drm = dependency('libdrm', required: get_option('live-previews-dmabuf')) +gbm = dependency('gbm', required: false) +drm = dependency('libdrm', required: false) if get_option('wayland-logout') == true wayland_logout = subproject('wayland-logout') @@ -45,8 +45,8 @@ if get_option('weather') == true add_project_arguments('-DHAVE_WEATHER=1', language: 'cpp') endif -if gbm.found() and drm.found() and not get_option('live-previews-dmabuf').disabled() - add_project_arguments('-DHAVE_DMABUF=1', language: 'cpp') +if not gbm.found() or not drm.found() + message('Live window previews require gbm and libdrm dependencies') endif if libpulse.found() diff --git a/meson_options.txt b/meson_options.txt index 12b64c52..3c50471b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -28,9 +28,3 @@ option( value: false, description: 'Install weather widgets and open-weather-fetch systemd service', ) -option( - 'live-previews-dmabuf', - type: 'feature', - value: 'auto', - description: 'Enable live window preview tooltips when hovering over window-list buttons in wf-panel' -) diff --git a/src/panel/meson.build b/src/panel/meson.build index 02e861a9..2f4fde16 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -72,10 +72,10 @@ else message('Weather disabled, weather widgets and owf service not included.') endif -if gbm.found() and drm.found() and not get_option('live-previews-dmabuf').disabled() +if gbm.found() and drm.found() deps += [gbm, drm] else - message('Development packages for gbm and drm are required to enable live window preview tooltips in wf-panel.') + message('Development packages for gbm and libdrm are required to enable live window preview tooltips in wf-panel.') endif executable( diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index bfcbe445..1736675f 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -63,29 +63,27 @@ static const struct ext_image_copy_capture_session_v1_listener recording_session .stopped = session_handle_stopped, }; -#ifdef HAVE_DMABUF static void dmabuf_created(void *data, struct zwp_linux_buffer_params_v1*, struct wl_buffer *wl_buffer) { TooltipMedia *tooltip_media = (TooltipMedia*)data; + if (tooltip_media->buffer) + { + wl_buffer_destroy(tooltip_media->buffer); + } + tooltip_media->buffer = wl_buffer; } static void dmabuf_failed(void *data, struct zwp_linux_buffer_params_v1*) -{ - TooltipMedia *tooltip_media = (TooltipMedia*)data; - - std::cerr << "Failed to create dmabuf, trying shm" << std::endl; - tooltip_media->window_list->live_previews_dmabuf = false; -} +{} static const struct zwp_linux_buffer_params_v1_listener params_listener = { .created = dmabuf_created, .failed = dmabuf_failed, }; -#endif // HAVE_DMABUF /* Copy Capture Callbacks */ @@ -174,7 +172,7 @@ static void frame_handle_failed(void *data, uint32_t reason) { TooltipMedia *tooltip_media = (TooltipMedia*)data; - std::cerr << "Failed to copy frame because reason: " << reason << std::endl; + ext_image_copy_capture_frame_v1_destroy(handle); tooltip_media->frame = nullptr; tooltip_media->frame_in_flight = false; @@ -243,7 +241,6 @@ void TooltipMedia::request_next_frame() { if ((current_buffer_width <= 0) || (current_buffer_height <= 0)) { - printf("%s invalid size\n", __func__); return; } @@ -304,7 +301,6 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_ { this->window_list = window_list; this->ext_handle = ext_handle; - this->shm = window_list->shm; set_can_shrink(true); set_content_fit(Gtk::ContentFit::CONTAIN); set_vexpand(false); @@ -347,24 +343,11 @@ TooltipMedia::~TooltipMedia() ext_image_copy_capture_session_v1_destroy(recording_session); } - if ( -#ifdef HAVE_DMABUF - !this->window_list->live_previews_dmabuf && -#endif // HAVE_DMABUF - this->shm_data && this->size) - { - if (munmap(this->shm_data, this->size) < 0) - { - perror("munmap failed"); - } - } - if (this->buffer) { wl_buffer_destroy(this->buffer); } -#ifdef HAVE_DMABUF if (this->params) { zwp_linux_buffer_params_v1_destroy(this->params); @@ -379,8 +362,6 @@ TooltipMedia::~TooltipMedia() { gbm_bo_destroy(this->bo); } - -#endif // HAVE_DMABUF } class WayfireToplevel::impl @@ -536,7 +517,7 @@ class WayfireToplevel::impl void set_tooltip_media() { - if (this->tooltip_media || !this->ext_handle) + if (this->tooltip_media || !this->ext_handle || !(bool)window_list->live_window_previews) { return; } @@ -734,7 +715,8 @@ class WayfireToplevel::impl return false; } - if (this->window_list->list_toplevels.empty()) + if (this->window_list->list_toplevels.empty() || !this->ext_handle || + !(bool)window_list->live_window_previews) { tooltip->set_text(title); } diff --git a/src/panel/widgets/window-list/toplevel.hpp b/src/panel/widgets/window-list/toplevel.hpp index 5ee9a6c1..8baa92ef 100644 --- a/src/panel/widgets/window-list/toplevel.hpp +++ b/src/panel/widgets/window-list/toplevel.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -9,17 +11,12 @@ #include #include #include +#include #include #include #include "wf-shell-app.hpp" #include "panel.hpp" -#ifdef HAVE_DMABUF - #include - #include - #include -#endif // HAVE_DMABUF - class WayfireWindowList; class WayfireWindowListBox; @@ -34,9 +31,7 @@ class TooltipMedia : public Gtk::Picture { public: WayfireWindowList *window_list = nullptr; - wl_shm *shm = nullptr; wl_buffer *buffer = nullptr; - void *shm_data = nullptr; ext_foreign_toplevel_handle_v1 *ext_handle = NULL; ext_image_copy_capture_frame_v1 *frame = NULL; ext_image_capture_source_v1 *copy_capture_source = NULL; @@ -48,14 +43,11 @@ class TooltipMedia : public Gtk::Picture uint32_t current_buffer_width = 0, width = -1; uint32_t current_buffer_height = 0, height = -1; uint32_t stride; - size_t size = 0; -#ifdef HAVE_DMABUF gbm_bo *bo = nullptr; zwp_linux_buffer_params_v1 *params = nullptr; void *dmabuf_data = nullptr; void *map_data = nullptr; -#endif // HAVE_DMABUF TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_handle_v1 *ext_handle); ~TooltipMedia(); diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index e22803c3..e3374194 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -4,9 +4,7 @@ #include "window-list.hpp" -#ifdef HAVE_DMABUF - #include -#endif // HAVE_DMABUF +#include #define DEFAULT_SIZE_PC 0.1 @@ -137,7 +135,6 @@ ext_foreign_toplevel_list_v1_listener toplevel_list_v1_impl = { .finished = handle_finished, }; -#ifdef HAVE_DMABUF static void dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *feedback) { WayfireWindowList *window_list = (WayfireWindowList*)data; @@ -167,7 +164,6 @@ static void dmabuf_feedback_main_device(void *data, struct zwp_linux_dmabuf_feed { perror("Failed to get DRM device from dev id"); std::cerr << "Trying shm" << std::endl; - window_list->live_previews_dmabuf = false; return; } @@ -184,7 +180,6 @@ static void dmabuf_feedback_main_device(void *data, struct zwp_linux_dmabuf_feed { perror("Failed to open drm device"); std::cerr << "Trying shm" << std::endl; - window_list->live_previews_dmabuf = false; return; } @@ -194,7 +189,6 @@ static void dmabuf_feedback_main_device(void *data, struct zwp_linux_dmabuf_feed close(drm_fd); perror("Failed to create gbm device"); std::cerr << "Trying shm" << std::endl; - window_list->live_previews_dmabuf = false; return; } @@ -228,17 +222,13 @@ static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_feedback_listen .tranche_formats = dmabuf_feedback_tranche_formats, .tranche_flags = dmabuf_feedback_tranche_flags, }; -#endif // HAVE_DMABUF static void registry_add_object(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { WayfireWindowList *window_list = (WayfireWindowList*)data; - if (strcmp(interface, 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) + 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, @@ -268,10 +258,7 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name &ext_foreign_toplevel_image_capture_source_manager_v1_interface, version); window_list->toplevel_capture_manager = toplevel_capture_manager; printf("set toplevel_capture_manager: %p\n", toplevel_capture_manager); - } - -#ifdef HAVE_DMABUF - else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) + } else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) { window_list->dmabuf = (zwp_linux_dmabuf_v1*)wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); @@ -282,7 +269,6 @@ static void registry_add_object(void *data, wl_registry *registry, uint32_t name window_list); } } -#endif // HAVE_DMABUF } static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) @@ -342,12 +328,6 @@ void WayfireWindowList::init(Gtk::Box *container) return; } -#ifdef HAVE_DMABUF - this->live_previews_dmabuf = this->dmabuf ? true : false; -#else - this->live_previews_dmabuf = false; -#endif // HAVE_DMABUF - scrolled_window.add_css_class("window-list"); scrolled_window.set_hexpand(true); @@ -472,9 +452,6 @@ void WayfireWindowList::handle_toplevel_closed(zwlr_foreign_toplevel_handle_v1 * toplevels.erase(toplevel); } -void WayfireWindowList::on_event(wf::json_t data) -{} - WayfireWindowList::WayfireWindowList(WayfireOutput *output) { this->output = output; @@ -494,9 +471,8 @@ WayfireWindowList::~WayfireWindowList() * when the window-list widget is unloaded. */ toplevels.clear(); - wl_shm_destroy(this->shm); zwlr_foreign_toplevel_manager_v1_destroy(this->manager); -#ifdef HAVE_DMABUF + if (this->dmabuf) { zwp_linux_dmabuf_v1_destroy(this->dmabuf); @@ -511,6 +487,4 @@ WayfireWindowList::~WayfireWindowList() { gbm_device_destroy(this->dmabuf_device); } - -#endif } diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 85a1f7a4..5e1e6eb6 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -19,7 +19,7 @@ struct WayfireListToplevel class WayfireToplevel; -class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubscriber +class WayfireWindowList : public Gtk::Box, public WayfireWidget { WfOption user_size{"panel/window_list_size"}; std::shared_ptr layout; @@ -31,7 +31,6 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget, public IIPCSubs std::unique_ptr> list_toplevels; wl_display *display; - wl_shm *shm = nullptr; zwlr_foreign_toplevel_manager_v1 *manager = NULL; ext_foreign_toplevel_list_v1 *foreign_toplevel_list = NULL; ext_image_copy_capture_manager_v1 *copy_capture_manager = NULL; @@ -84,21 +83,12 @@ 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"}; + WfOption live_window_previews{"panel/live_window_previews"}; void handle_new_wl_output(wl_output *output); - void on_event(wf::json_t data) override; - std::shared_ptr ipc_client; - bool normal_title_tooltips = false; - void enable_normal_tooltips_flag(bool enable); - uint64_t live_window_preview_view_id = 0; - void enable_ipc(bool enable); - bool live_previews_dmabuf = true; -#ifdef HAVE_DMABUF zwp_linux_dmabuf_feedback_v1 *feedback = nullptr; zwp_linux_dmabuf_v1 *dmabuf = nullptr; gbm_device *dmabuf_device = nullptr; -#endif // HAVE_DMABUF private: int get_default_button_width(); From e8ad37679645b76a1f89134b5bfa7d553fbad799 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Tue, 9 Jun 2026 22:18:23 -0600 Subject: [PATCH 08/10] window-list: Use get_rowstride() from Gdk::Pixbuf for stride --- src/panel/widgets/window-list/toplevel.cpp | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 1736675f..97f77a15 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -68,11 +68,6 @@ static void dmabuf_created(void *data, struct zwp_linux_buffer_params_v1*, { TooltipMedia *tooltip_media = (TooltipMedia*)data; - if (tooltip_media->buffer) - { - wl_buffer_destroy(tooltip_media->buffer); - } - tooltip_media->buffer = wl_buffer; } @@ -136,20 +131,25 @@ static void frame_handle_ready(void *data, tooltip_media->height, stride); - auto w = 500; - auto h = tooltip_media->height * (500.0f / tooltip_media->width); + gbm_bo_unmap(tooltip_media->bo, map_data); + + uint32_t w = 500; + uint32_t h = tooltip_media->height * (500.0f / tooltip_media->width); auto scaled_pixbuf = pixbuf->scale_simple( w, h, Gdk::InterpType::BILINEAR); + w = scaled_pixbuf->get_width(); + h = scaled_pixbuf->get_height(); + uint32_t s = scaled_pixbuf->get_rowstride(); + /* Swap red and blue channels */ - size_t size = w * h * 4; + size_t size = s * h; pixel_data = scaled_pixbuf->get_pixels(); std::shared_ptr bytes = Glib::Bytes::create((unsigned char*)pixel_data, size); if (!bytes) { - gbm_bo_unmap(tooltip_media->bo, map_data); return; } @@ -157,14 +157,13 @@ static void frame_handle_ready(void *data, builder->set_bytes(bytes); builder->set_width(w); builder->set_height(h); - builder->set_stride(w * 4); + builder->set_stride(s); builder->set_format(Gdk::MemoryFormat::B8G8R8A8); auto texture = builder->build(); tooltip_media->set_paintable(texture); - - gbm_bo_unmap(tooltip_media->bo, map_data); + tooltip_media->frame_in_flight = false; } static void frame_handle_failed(void *data, @@ -320,7 +319,7 @@ TooltipMedia::TooltipMedia(WayfireWindowList *window_list, ext_foreign_toplevel_ this->request_next_frame(); return true; - }, 33); + }, 20); } TooltipMedia::~TooltipMedia() From 92054c68886c8d582fa9442a1de7e9ca17a7a7e0 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 11 Jun 2026 01:06:31 -0600 Subject: [PATCH 09/10] window-list: Destroy handles and globals from ext binds This effectively fixes a crash on window-list widget unload. --- src/panel/widgets/window-list/window-list.cpp | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index e3374194..21f42ddd 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -471,7 +471,32 @@ WayfireWindowList::~WayfireWindowList() * when the window-list widget is unloaded. */ toplevels.clear(); - zwlr_foreign_toplevel_manager_v1_destroy(this->manager); + for (auto & list_toplevel : list_toplevels) + { + ext_foreign_toplevel_handle_v1_destroy(list_toplevel.first); + } + + list_toplevels.clear(); + + if (this->manager) + { + zwlr_foreign_toplevel_manager_v1_destroy(this->manager); + } + + if (this->foreign_toplevel_list) + { + ext_foreign_toplevel_list_v1_destroy(this->foreign_toplevel_list); + } + + if (this->copy_capture_manager) + { + ext_image_copy_capture_manager_v1_destroy(this->copy_capture_manager); + } + + if (this->toplevel_capture_manager) + { + ext_foreign_toplevel_image_capture_source_manager_v1_destroy(this->toplevel_capture_manager); + } if (this->dmabuf) { From 68880c64cc61108e99cd9a939b90bcba6345ec97 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sat, 13 Jun 2026 01:15:56 -0600 Subject: [PATCH 10/10] window-list: Require ext-toplevel and wf-copy-capture plugins to enable live preview tooltips --- src/panel/widgets/window-list/toplevel.cpp | 7 +++--- src/panel/widgets/window-list/window-list.cpp | 24 +++++++++++++++---- src/panel/widgets/window-list/window-list.hpp | 3 ++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/panel/widgets/window-list/toplevel.cpp b/src/panel/widgets/window-list/toplevel.cpp index 97f77a15..e952cd83 100644 --- a/src/panel/widgets/window-list/toplevel.cpp +++ b/src/panel/widgets/window-list/toplevel.cpp @@ -516,7 +516,8 @@ class WayfireToplevel::impl void set_tooltip_media() { - if (this->tooltip_media || !this->ext_handle || !(bool)window_list->live_window_previews) + if (this->tooltip_media || !this->window_list->toplevel_capture_manager || + !(bool)window_list->live_window_previews || !this->ext_handle) { return; } @@ -714,8 +715,8 @@ class WayfireToplevel::impl return false; } - if (this->window_list->list_toplevels.empty() || !this->ext_handle || - !(bool)window_list->live_window_previews) + if (this->window_list->list_toplevels.empty() || !this->window_list->toplevel_capture_manager || + !(bool)window_list->live_window_previews || !this->ext_handle) { tooltip->set_text(title); } diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 21f42ddd..9d38a7d1 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -314,20 +314,32 @@ void WayfireWindowList::init(Gtk::Box *container) this->display = display; - wl_registry *registry = wl_display_get_registry(display); + registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(display); - wl_registry_destroy(registry); - - if (!this->copy_capture_manager) + if (!this->manager) { std::cerr << "Compositor doesn't support" << - " ext-image-copy-capture." << + " ext-image-copy-capture-v1." << "The window-list widget will not be initialized." << std::endl; return; } + if (!this->foreign_toplevel_list) + { + std::cerr << "Compositor doesn't support" << + " ext-foreign-toplevel-list-v1." << + "Live window previews cannot be enabled." << std::endl; + } + + if (!this->toplevel_capture_manager) + { + std::cerr << "Compositor doesn't support" << + " ext-foreign-toplevel-image-copy-capture-v1." << + "Live window previews cannot be enabled." << std::endl; + } + scrolled_window.add_css_class("window-list"); scrolled_window.set_hexpand(true); @@ -478,6 +490,8 @@ WayfireWindowList::~WayfireWindowList() list_toplevels.clear(); + wl_registry_destroy(registry); + if (this->manager) { zwlr_foreign_toplevel_manager_v1_destroy(this->manager); diff --git a/src/panel/widgets/window-list/window-list.hpp b/src/panel/widgets/window-list/window-list.hpp index 5e1e6eb6..daf31022 100644 --- a/src/panel/widgets/window-list/window-list.hpp +++ b/src/panel/widgets/window-list/window-list.hpp @@ -21,6 +21,8 @@ class WayfireToplevel; class WayfireWindowList : public Gtk::Box, public WayfireWidget { + wl_display *display; + wl_registry *registry; WfOption user_size{"panel/window_list_size"}; std::shared_ptr layout; @@ -30,7 +32,6 @@ class WayfireWindowList : public Gtk::Box, public WayfireWidget std::map> list_toplevels; - wl_display *display; zwlr_foreign_toplevel_manager_v1 *manager = NULL; ext_foreign_toplevel_list_v1 *foreign_toplevel_list = NULL; ext_image_copy_capture_manager_v1 *copy_capture_manager = NULL;