summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Kareh <[email protected]>2019-04-09 16:04:06 +0300
committerraveit65 <[email protected]>2019-05-09 21:34:47 +0200
commitdc4c4f08c716c6275979904d837e3cded05f0565 (patch)
treefa68ca4cdde365ff88a5326f037f032deb6a3d7b
parenta2cdef91974fbe83995791e98f19ed58ec04a43c (diff)
downloadmate-panel-dc4c4f08c716c6275979904d837e3cded05f0565.tar.bz2
mate-panel-dc4c4f08c716c6275979904d837e3cded05f0565.tar.xz
window-list: Show window previews on hover
When hovering on taskbar entries, display a popup with a thumbnail of the associated window. Popup goes away when leaving the button. If a window has not been mapped since the applet started, it will not display a thumbnail. Once mapped, however, it will use its latest available thumbnail as preview, even when minimized. Thumbnails can be turned on or off, and the size can be changed in the applet preferences dialog.
-rw-r--r--applets/wncklet/org.mate.panel.applet.window-list.gschema.xml.in10
-rw-r--r--applets/wncklet/window-list.c270
-rw-r--r--applets/wncklet/window-list.ui144
3 files changed, 417 insertions, 7 deletions
diff --git a/applets/wncklet/org.mate.panel.applet.window-list.gschema.xml.in b/applets/wncklet/org.mate.panel.applet.window-list.gschema.xml.in
index 0f2d871d..08647c21 100644
--- a/applets/wncklet/org.mate.panel.applet.window-list.gschema.xml.in
+++ b/applets/wncklet/org.mate.panel.applet.window-list.gschema.xml.in
@@ -10,6 +10,16 @@
<summary>Show windows from all workspaces</summary>
<description>If true, the window list will show windows from all workspaces. Otherwise it will only display windows from the current workspace.</description>
</key>
+ <key name="show-window-thumbnails" type="b">
+ <default>true</default>
+ <summary>Display window thumbnails on hover</summary>
+ <description>If true, then when hovering over a taskbar item, a thumbnail of the window will appear. It will go away as soon as the mouse leaves the item.</description>
+ </key>
+ <key name="thumbnail-window-size" type="i">
+ <default>200</default>
+ <summary>Size of window thumbnails</summary>
+ <description>Size in pixels of the window preview thumbnail. The largest between width and height will use this value, the other one will be calculated to maintain the correct aspect ratio.</description>
+ </key>
<key name="group-windows" enum="org.mate.panel.applet.window-list.GroupingType">
<default>'never'</default>
<summary>When to group windows</summary>
diff --git a/applets/wncklet/window-list.c b/applets/wncklet/window-list.c
index 3cdcd834..833f0cce 100644
--- a/applets/wncklet/window-list.c
+++ b/applets/wncklet/window-list.c
@@ -19,6 +19,7 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>
#include <gio/gio.h>
@@ -35,8 +36,11 @@
typedef struct {
GtkWidget* applet;
GtkWidget* tasklist;
+ GtkWidget* preview;
gboolean include_all_workspaces;
+ gboolean show_window_thumbnails;
+ gint thumbnail_size;
WnckTasklistGroupingType grouping;
gboolean move_unminimized_windows;
@@ -52,6 +56,9 @@ typedef struct {
GtkWidget* properties_dialog;
GtkWidget* show_current_radio;
GtkWidget* show_all_radio;
+ GtkWidget* show_thumbnails_radio;
+ GtkWidget* hide_thumbnails_radio;
+ GtkWidget* thumbnail_size_spin;
GtkWidget* never_group_radio;
GtkWidget* auto_group_radio;
GtkWidget* always_group_radio;
@@ -139,6 +146,171 @@ static void applet_change_background(MatePanelApplet* applet, MatePanelAppletBac
}
}
+static GdkPixbuf *preview_window_thumbnail (WnckWindow *wnck_window, TasklistData *tasklist)
+{
+ GdkWindow *window;
+ GdkPixbuf *screenshot;
+ GdkPixbuf *thumbnail;
+ guchar *pixels;
+ double ratio;
+ int width, height;
+ int scale;
+
+ window = gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), wnck_window_get_xid (wnck_window));
+
+ if (window == NULL)
+ return NULL;
+
+ scale = gdk_window_get_scale_factor (window);
+ width = gdk_window_get_width (window) * scale;
+ height = gdk_window_get_height (window) * scale;
+
+ /* Generate window screenshot for preview */
+ screenshot = gdk_pixbuf_get_from_window (window, 0, 0, width / scale, height / scale);
+ g_object_unref (window);
+
+ if (screenshot == NULL)
+ return NULL;
+
+ /* Determine whether the contents of the screenshot are empty */
+ pixels = gdk_pixbuf_get_pixels (screenshot);
+ if (!g_strcmp0 ((const char *)pixels, ""))
+ {
+ g_object_unref (screenshot);
+ return NULL;
+ }
+
+ /* Scale to configured size while maintaining aspect ratio */
+ if (width > height)
+ {
+ ratio = (double) height / (double) width;
+ width = MIN(width, tasklist->thumbnail_size);
+ height = width * ratio;
+ }
+ else
+ {
+ ratio = (double) width / (double) height;
+ height = MIN(height, tasklist->thumbnail_size);
+ width = height * ratio;
+ }
+
+ thumbnail = gdk_pixbuf_scale_simple (screenshot, width, height, GDK_INTERP_BILINEAR);
+ g_object_unref (screenshot);
+
+ return thumbnail;
+}
+
+#define PREVIEW_PADDING 5
+static void preview_window_reposition (TasklistData *tasklist, GdkPixbuf *thumbnail)
+{
+ GdkMonitor *monitor;
+ GdkRectangle monitor_geom;
+ int x_pos, y_pos;
+ int width, height;
+
+ width = gdk_pixbuf_get_width (thumbnail);
+ height = gdk_pixbuf_get_height (thumbnail);
+
+ /* Resize window to fit thumbnail */
+ gtk_window_resize (GTK_WINDOW (tasklist->preview), width, height);
+
+ /* Set position at pointer, then re-adjust from there to just outside of the pointer */
+ gtk_window_set_position (GTK_WINDOW (tasklist->preview), GTK_WIN_POS_MOUSE);
+ gtk_window_get_position (GTK_WINDOW (tasklist->preview), &x_pos, &y_pos);
+
+ /* Get geometry of monitor where tasklist is located to calculate correct position of preview */
+ monitor = gdk_display_get_monitor_at_point (gdk_display_get_default (), x_pos, y_pos);
+ gdk_monitor_get_geometry (monitor, &monitor_geom);
+
+ /* Add padding to clear the panel */
+ switch (mate_panel_applet_get_orient (MATE_PANEL_APPLET (tasklist->applet)))
+ {
+ case MATE_PANEL_APPLET_ORIENT_LEFT:
+ x_pos = monitor_geom.width + monitor_geom.x - (width + tasklist->size) - PREVIEW_PADDING;
+ break;
+ case MATE_PANEL_APPLET_ORIENT_RIGHT:
+ x_pos = tasklist->size + PREVIEW_PADDING;
+ break;
+ case MATE_PANEL_APPLET_ORIENT_UP:
+ y_pos = monitor_geom.height + monitor_geom.y - (height + tasklist->size) - PREVIEW_PADDING;
+ break;
+ case MATE_PANEL_APPLET_ORIENT_DOWN:
+ default:
+ y_pos = tasklist->size + PREVIEW_PADDING;
+ break;
+ }
+
+ gtk_window_move (GTK_WINDOW (tasklist->preview), x_pos, y_pos);
+}
+
+static gboolean preview_window_draw (GtkWidget *widget, cairo_t *cr, GdkPixbuf *thumbnail)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_render_icon (context, cr, thumbnail, 0, 0);
+
+ return FALSE;
+}
+
+static gboolean applet_enter_notify_event (WnckTasklist *tl, GList *wnck_windows, TasklistData *tasklist)
+{
+ GdkPixbuf *thumbnail;
+ WnckWindow *wnck_window = NULL;
+ int n_windows;
+
+ if (tasklist->preview != NULL)
+ {
+ gtk_widget_destroy (tasklist->preview);
+ tasklist->preview = NULL;
+ }
+
+ if (!tasklist->show_window_thumbnails || wnck_windows == NULL)
+ return FALSE;
+
+ n_windows = g_list_length (wnck_windows);
+ /* TODO: Display a list of stacked thumbnails for grouped windows. */
+ if (n_windows == 1)
+ {
+ GList* l = wnck_windows;
+ if (l != NULL)
+ wnck_window = (WnckWindow*)l->data;
+ }
+
+ if (wnck_window == NULL)
+ return FALSE;
+
+ thumbnail = preview_window_thumbnail (wnck_window, tasklist);
+
+ if (thumbnail == NULL)
+ return FALSE;
+
+ /* Create window to display preview */
+ tasklist->preview = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_widget_set_app_paintable (tasklist->preview, TRUE);
+ gtk_window_set_resizable (GTK_WINDOW (tasklist->preview), TRUE);
+
+ preview_window_reposition (tasklist, thumbnail);
+
+ gtk_widget_show (tasklist->preview);
+
+ g_signal_connect_data (G_OBJECT (tasklist->preview), "draw", G_CALLBACK (preview_window_draw), thumbnail, (GClosureNotify) g_object_unref, 0);
+
+ return FALSE;
+}
+
+static gboolean applet_leave_notify_event (WnckTasklist *tl, GList *wnck_windows, TasklistData *tasklist)
+{
+ if (tasklist->preview != NULL)
+ {
+ gtk_widget_destroy (tasklist->preview);
+ tasklist->preview = NULL;
+ }
+
+ return FALSE;
+}
+
static void applet_change_pixel_size(MatePanelApplet* applet, gint size, TasklistData* tasklist)
{
if (tasklist->size == size)
@@ -227,6 +399,55 @@ static void display_all_workspaces_changed(GSettings* settings, gchar* key, Task
tasklist_properties_update_content_radio(tasklist);
}
+static void tasklist_update_thumbnails_radio(TasklistData* tasklist)
+{
+ GtkWidget* button;
+
+ if (tasklist->show_thumbnails_radio == NULL || tasklist->hide_thumbnails_radio == NULL)
+ return;
+
+ if (tasklist->show_window_thumbnails)
+ {
+ button = tasklist->show_thumbnails_radio;
+ }
+ else
+ {
+ button = tasklist->hide_thumbnails_radio;
+ }
+
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+}
+
+static void window_thumbnails_changed(GSettings *settings, gchar* key, TasklistData* tasklist)
+{
+ gboolean value;
+
+ value = g_settings_get_boolean(settings, key);
+
+ tasklist->show_window_thumbnails = (value != 0);
+
+ tasklist_update_thumbnails_radio(tasklist);
+}
+
+static void tasklist_update_thumbnail_size_spin(TasklistData* tasklist)
+{
+ GtkWidget* button;
+
+ if (!tasklist->thumbnail_size)
+ return;
+
+ button = tasklist->thumbnail_size_spin;
+
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), (gdouble)tasklist->thumbnail_size);
+}
+
+static void thumbnail_size_changed(GSettings *settings, gchar* key, TasklistData* tasklist)
+{
+ tasklist->thumbnail_size = g_settings_get_int(settings, key);
+ tasklist_update_thumbnail_size_spin(tasklist);
+}
+
static GtkWidget* get_grouping_button(TasklistData* tasklist, WnckTasklistGroupingType type)
{
switch (type)
@@ -304,6 +525,14 @@ static void setup_gsettings(TasklistData* tasklist)
G_CALLBACK (display_all_workspaces_changed),
tasklist);
g_signal_connect (tasklist->settings,
+ "changed::show-window-thumbnails",
+ G_CALLBACK (window_thumbnails_changed),
+ tasklist);
+ g_signal_connect (tasklist->settings,
+ "changed::thumbnail-window-size",
+ G_CALLBACK (thumbnail_size_changed),
+ tasklist);
+ g_signal_connect (tasklist->settings,
"changed::group-windows",
G_CALLBACK (group_windows_changed),
tasklist);
@@ -421,6 +650,10 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
tasklist->include_all_workspaces = g_settings_get_boolean (tasklist->settings, "display-all-workspaces");
+ tasklist->show_window_thumbnails = g_settings_get_boolean (tasklist->settings, "show-window-thumbnails");
+
+ tasklist->thumbnail_size = g_settings_get_int (tasklist->settings, "thumbnail-window-size");
+
tasklist->grouping = g_settings_get_enum (tasklist->settings, "group-windows");
tasklist->move_unminimized_windows = g_settings_get_boolean (tasklist->settings, "move-unminimized-windows");
@@ -451,6 +684,8 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
wnck_tasklist_set_icon_loader(WNCK_TASKLIST(tasklist->tasklist), icon_loader_func, tasklist, NULL);
g_signal_connect(G_OBJECT(tasklist->tasklist), "destroy", G_CALLBACK(destroy_tasklist), tasklist);
+ g_signal_connect(G_OBJECT(tasklist->tasklist), "task_enter_notify", G_CALLBACK(applet_enter_notify_event), tasklist);
+ g_signal_connect(G_OBJECT(tasklist->tasklist), "task_leave_notify", G_CALLBACK(applet_leave_notify_event), tasklist);
g_signal_connect(G_OBJECT(tasklist->applet), "size_allocate", G_CALLBACK(applet_size_allocate), tasklist);
@@ -584,6 +819,16 @@ static void group_windows_toggled(GtkToggleButton* button, TasklistData* tasklis
}
}
+static void show_thumbnails_toggled(GtkToggleButton* button, TasklistData* tasklist)
+{
+ g_settings_set_boolean(tasklist->settings, "show-window-thumbnails", gtk_toggle_button_get_active(button));
+}
+
+static void thumbnail_size_spin_changed(GtkSpinButton* button, TasklistData* tasklist)
+{
+ g_settings_set_int(tasklist->settings, "thumbnail-window-size", gtk_spin_button_get_value_as_int(button));
+}
+
static void move_minimized_toggled(GtkToggleButton* button, TasklistData* tasklist)
{
g_settings_set_boolean(tasklist->settings, "move-unminimized-windows", gtk_toggle_button_get_active(button));
@@ -627,6 +872,7 @@ static void setup_sensitivity(TasklistData* tasklist, GtkBuilder* builder, const
static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
{
GtkWidget* button;
+ GtkAdjustment *adjustment;
tasklist->show_current_radio = WID("show_current_radio");
tasklist->show_all_radio = WID("show_all_radio");
@@ -639,6 +885,17 @@ static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
setup_sensitivity(tasklist, builder, "never_group_radio", "auto_group_radio", "always_group_radio", "group-windows" /* key */);
+ tasklist->show_thumbnails_radio = WID("show_thumbnails_radio");
+ tasklist->hide_thumbnails_radio = WID("hide_thumbnails_radio");
+ tasklist->thumbnail_size_spin = WID("thumbnail_size_spin");
+
+ setup_sensitivity(tasklist, builder, "show_thumbnails_radio", "hide_thumbnails_radio", NULL, "show-window-thumbnails" /* key */);
+ gtk_widget_set_sensitive(tasklist->thumbnail_size_spin, TRUE);
+ adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(tasklist->thumbnail_size_spin));
+ gtk_adjustment_set_lower (adjustment, 0);
+ gtk_adjustment_set_upper (adjustment, 999);
+ gtk_adjustment_set_step_increment (adjustment, 1);
+
tasklist->minimized_windows_label = WID("minimized_windows_label");
tasklist->move_minimized_radio = WID("move_minimized_radio");
tasklist->change_workspace_radio = WID("change_workspace_radio");
@@ -656,6 +913,13 @@ static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
g_signal_connect(G_OBJECT(tasklist->auto_group_radio), "toggled", (GCallback) group_windows_toggled, tasklist);
g_signal_connect(G_OBJECT(tasklist->always_group_radio), "toggled", (GCallback) group_windows_toggled, tasklist);
+ /* show thumbnails on hover: */
+ tasklist_update_thumbnails_radio(tasklist);
+ g_signal_connect(G_OBJECT(tasklist->show_thumbnails_radio), "toggled", (GCallback) show_thumbnails_toggled, tasklist);
+ /* change thumbnail size: */
+ tasklist_update_thumbnail_size_spin(tasklist);
+ g_signal_connect(G_OBJECT(tasklist->thumbnail_size_spin), "value-changed", (GCallback) thumbnail_size_spin_changed, tasklist);
+
/* move window when unminimizing: */
tasklist_update_unminimization_radio(tasklist);
g_signal_connect(G_OBJECT(tasklist->move_minimized_radio), "toggled", (GCallback) move_minimized_toggled, tasklist);
@@ -698,6 +962,8 @@ static void destroy_tasklist(GtkWidget* widget, TasklistData* tasklist)
{
g_signal_handlers_disconnect_by_data (G_OBJECT (tasklist->applet), tasklist);
+ g_signal_handlers_disconnect_by_data (G_OBJECT (tasklist->tasklist), tasklist);
+
g_signal_handlers_disconnect_by_data (tasklist->settings, tasklist);
g_object_unref(tasklist->settings);
@@ -705,6 +971,8 @@ static void destroy_tasklist(GtkWidget* widget, TasklistData* tasklist)
if (tasklist->properties_dialog)
gtk_widget_destroy(tasklist->properties_dialog);
- g_free(tasklist);
+ if (tasklist->preview)
+ gtk_widget_destroy(tasklist->preview);
+ g_free(tasklist);
}
diff --git a/applets/wncklet/window-list.ui b/applets/wncklet/window-list.ui
index 851a72a0..174285a0 100644
--- a/applets/wncklet/window-list.ui
+++ b/applets/wncklet/window-list.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
+<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkImage" id="image1">
@@ -17,6 +17,9 @@
<property name="border_width">5</property>
<property name="title" translatable="yes">Window List Preferences</property>
<property name="type_hint">normal</property>
+ <child>
+ <placeholder/>
+ </child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox2">
<property name="visible">True</property>
@@ -158,6 +161,138 @@
</packing>
</child>
<child>
+ <object class="GtkBox" id="vbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Window Thumbnails</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="show_thumbnails_radio">
+ <property name="label" translatable="yes">Show _thumbnails on hover</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="hide_thumbnails_radio">
+ <property name="label" translatable="yes">_Hide thumbnails on hover</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">show_thumbnails_radio</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Thumbnail width in pixels. Window aspect ratio will be maintained.</property>
+ <property name="label" translatable="yes">Thumbnail width:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="thumbnail_size_spin">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">3</property>
+ <property name="caps_lock_warning">False</property>
+ <property name="placeholder_text" translatable="yes">px</property>
+ <property name="input_purpose">number</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ <property name="value">200</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkBox" id="vbox11">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -251,7 +386,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
<child>
@@ -332,7 +467,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">3</property>
+ <property name="position">4</property>
</packing>
</child>
</object>
@@ -348,8 +483,5 @@
<action-widget response="-11">help_button</action-widget>
<action-widget response="0">done_button</action-widget>
</action-widgets>
- <child>
- <placeholder/>
- </child>
</object>
</interface>