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