summaryrefslogtreecommitdiff
path: root/applets/wncklet/window-list.c
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 /applets/wncklet/window-list.c
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.
Diffstat (limited to 'applets/wncklet/window-list.c')
-rw-r--r--applets/wncklet/window-list.c270
1 files changed, 269 insertions, 1 deletions
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);
}