summaryrefslogtreecommitdiff
path: root/applets/wncklet/wayland-backend.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/wncklet/wayland-backend.c')
-rw-r--r--applets/wncklet/wayland-backend.c420
1 files changed, 402 insertions, 18 deletions
diff --git a/applets/wncklet/wayland-backend.c b/applets/wncklet/wayland-backend.c
index 658eb6d3..30469894 100644
--- a/applets/wncklet/wayland-backend.c
+++ b/applets/wncklet/wayland-backend.c
@@ -26,27 +26,50 @@
#endif
#include <gdk/gdkwayland.h>
+#include <gio/gdesktopappinfo.h>
#include "wayland-backend.h"
#include "wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h"
-static const int window_button_width = 140;
+/*shorter than wnck-tasklist due to common use of larger fonts*/
+#define TASKLIST_TEXT_MAX_WIDTH 16
+
+/*In the future this could be changable from the panel-prefs dialog*/
+static const int max_button_width = 180;
+static const int icon_size = 16;
+int full_button_width;
+
+typedef struct
+{
+ GtkWidget *menu;
+ GtkWidget *maximize;
+ GtkWidget *minimize;
+ GtkWidget *on_top;
+ GtkWidget *close;
+} ContextMenu;
typedef struct
{
GtkWidget *list;
GtkWidget *outer_box;
+ ContextMenu *context_menu;
struct zwlr_foreign_toplevel_manager_v1 *manager;
} TasklistManager;
typedef struct
{
GtkWidget *button;
+ GtkWidget *icon;
GtkWidget *label;
struct zwlr_foreign_toplevel_handle_v1 *toplevel;
gboolean active;
+ gboolean maximized;
+ gboolean minimized;
+ gboolean fullscreen;
} ToplevelTask;
+static int tasklist_invocations = 0;
+
static const char *tasklist_manager_key = "tasklist_manager";
static const char *toplevel_task_key = "toplevel_task";
@@ -55,9 +78,12 @@ static struct wl_registry *wl_registry_global = NULL;
static uint32_t foreign_toplevel_manager_global_id = 0;
static uint32_t foreign_toplevel_manager_global_version = 0;
-static TasklistManager *tasklist_manager_new ();
static ToplevelTask *toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *handle);
+guint buttons, tasklist_width;
+
+gboolean window_hidden;
+
static void
wl_registry_handle_global (void *_data,
struct wl_registry *registry,
@@ -92,7 +118,7 @@ static const struct wl_registry_listener wl_registry_listener = {
};
static void
-wayland_tasklist_init_if_needed ()
+wayland_tasklist_init_if_needed (void)
{
if (has_initialized)
return;
@@ -120,7 +146,7 @@ foreign_toplevel_manager_handle_toplevel (void *data,
{
TasklistManager *tasklist = data;
ToplevelTask *task = toplevel_task_new (tasklist, toplevel);
- gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 2);
+ gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
}
static void
@@ -162,16 +188,84 @@ tasklist_manager_disconnected_from_widget (TasklistManager *tasklist)
if (tasklist->manager)
zwlr_foreign_toplevel_manager_v1_stop (tasklist->manager);
+
+ if (tasklist->context_menu)
+ {
+ gtk_widget_destroy (tasklist->context_menu->menu);
+ g_free (tasklist->context_menu);
+ tasklist->context_menu = NULL;
+ }
+}
+
+static void
+menu_on_maximize (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task->toplevel) {
+ if (task->maximized) {
+ zwlr_foreign_toplevel_handle_v1_unset_maximized (task->toplevel);
+ } else {
+ zwlr_foreign_toplevel_handle_v1_set_maximized (task->toplevel);
+ }
+ }
+}
+
+static void
+menu_on_minimize (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task->toplevel) {
+ if (task->minimized) {
+ zwlr_foreign_toplevel_handle_v1_unset_minimized (task->toplevel);
+ } else {
+ zwlr_foreign_toplevel_handle_v1_set_minimized (task->toplevel);
+ }
+ }
+}
+
+static void
+menu_on_close (GtkMenuItem *item, gpointer user_data)
+{
+ ToplevelTask *task = g_object_get_data (G_OBJECT (item), toplevel_task_key);
+ if (task->toplevel) {
+ zwlr_foreign_toplevel_handle_v1_close (task->toplevel);
+ }
+}
+
+static ContextMenu *
+context_menu_new ()
+{
+ ContextMenu *menu = g_new0 (ContextMenu, 1);
+ menu->menu = gtk_menu_new ();
+ menu->maximize = gtk_menu_item_new ();
+ menu->minimize = gtk_menu_item_new ();
+ menu->on_top = gtk_check_menu_item_new_with_label ("Always On Top");
+ menu->close = gtk_menu_item_new_with_label ("Close");
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->maximize);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->minimize);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->on_top);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), gtk_separator_menu_item_new ());
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), menu->close);
+
+ gtk_widget_show_all (menu->menu);
+
+ g_signal_connect (menu->maximize, "activate", G_CALLBACK (menu_on_maximize), NULL);
+ g_signal_connect (menu->minimize, "activate", G_CALLBACK (menu_on_minimize), NULL);
+ g_signal_connect (menu->close, "activate", G_CALLBACK (menu_on_close), NULL);
+ gtk_widget_set_sensitive (menu->on_top, FALSE);
+ return menu;
}
static TasklistManager *
-tasklist_manager_new ()
+tasklist_manager_new (void)
{
if (!foreign_toplevel_manager_global_id)
return NULL;
TasklistManager *tasklist = g_new0 (TasklistManager, 1);
- tasklist->list = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ tasklist->list = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_set_homogeneous (GTK_BOX (tasklist->list), TRUE);
tasklist->outer_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (tasklist->outer_box), tasklist->list, FALSE, FALSE, 0);
@@ -187,6 +281,7 @@ tasklist_manager_new ()
tasklist_manager_key,
tasklist,
(GDestroyNotify)tasklist_manager_disconnected_from_widget);
+ tasklist->context_menu = context_menu_new ();
return tasklist;
}
@@ -208,7 +303,27 @@ foreign_toplevel_handle_app_id (void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel,
const char *app_id)
{
- /* ignore */
+ ToplevelTask *task = data;
+
+ gchar *app_id_lower = g_utf8_strdown (app_id, -1);
+ gchar *desktop_app_id = g_strdup_printf ("%s.desktop", app_id_lower);
+ GDesktopAppInfo *app_info = g_desktop_app_info_new (desktop_app_id);
+
+ if (app_info) {
+ GIcon *icon = g_app_info_get_icon (G_APP_INFO (app_info));
+ if (icon) {
+ gtk_image_set_from_gicon (GTK_IMAGE (task->icon), icon, GTK_ICON_SIZE_MENU);
+ goto cleanup;
+ }
+ }
+ gtk_image_set_from_icon_name (GTK_IMAGE (task->icon), app_id_lower, GTK_ICON_SIZE_MENU);
+
+cleanup:
+ if (app_info) {
+ g_object_unref (G_OBJECT (app_info));
+ }
+ g_free (app_id_lower);
+ g_free (desktop_app_id);
}
static void
@@ -235,6 +350,9 @@ foreign_toplevel_handle_state (void *data,
ToplevelTask *task = data;
task->active = FALSE;
+ task->maximized = FALSE;
+ task->minimized = FALSE;
+ task->fullscreen = FALSE;
enum zwlr_foreign_toplevel_handle_v1_state *i;
wl_array_for_each (i, state)
@@ -244,7 +362,15 @@ foreign_toplevel_handle_state (void *data,
case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED:
task->active = TRUE;
break;
-
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED:
+ task->maximized = TRUE;
+ break;
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED:
+ task->minimized = TRUE;
+ break;
+ case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN:
+ task->fullscreen = TRUE;
+ break;
default:
break;
}
@@ -261,14 +387,144 @@ foreign_toplevel_handle_done (void *data,
}
static void
+adjust_buttons (GtkContainer *outer_box, int button_space, int real_buttons, ToplevelTask *task)
+{
+ GtkWidget *widget, *button, *box;
+
+ /*catch the case of an added button that can be missed
+ *Note that button space can come up zero on a first button
+ */
+ if (real_buttons < 2)
+ {
+ if(task)
+ {
+ gtk_widget_set_size_request (task->button, full_button_width, -1);
+ }
+ }
+
+ if ((task) && (button_space > 0) && (button_space < icon_size * 3))
+ {
+ gtk_widget_hide (task->icon);
+ }
+ else if (task)
+ {
+ gtk_widget_show (task->icon);
+ }
+
+ if ((task) && (button_space > 0) && (button_space < icon_size))
+ {
+ gtk_widget_hide (task->label);
+ }
+ else if (task)
+ {
+ gtk_widget_show (task->label);
+ }
+
+ GList* children = gtk_container_get_children (GTK_CONTAINER (outer_box));
+
+ while (children != NULL)
+ {
+ button = GTK_WIDGET (children->data);
+ box = gtk_bin_get_child (GTK_BIN (button));
+
+ if ((real_buttons < 2) || (real_buttons * full_button_width < tasklist_width * 0.75))
+ {
+ gtk_widget_set_size_request (button, full_button_width, -1);
+ gtk_widget_show_all (button);
+ return;
+ }
+ else
+ {
+ gtk_widget_set_size_request (button, MIN(button_space, full_button_width), -1);
+ }
+
+ /* if the number of buttons forces width to less than 3x the icon size, hide the icons
+ * if the number of buttons forces width to less than the icon size, hide the labels too.
+ * This is roughy the same behavior as on x11
+ * To find the icon and label we must iterate through the children of the box we packed
+ * into the button, there are two of them
+ */
+
+ GList* contents = gtk_container_get_children (GTK_CONTAINER (box));
+ while (contents != NULL)
+ {
+ widget = GTK_WIDGET (contents->data);
+ /*Show or hide the icon*/
+ if (GTK_IS_IMAGE (widget))
+ {
+ if ((button_space < icon_size * 3) && (button_space > 1))
+ gtk_widget_hide (widget);
+
+ else
+ gtk_widget_show (widget);
+
+ }
+
+ /*Show or hide the label*/
+ if (GTK_IS_LABEL (widget))
+ {
+ if ((button_space < icon_size) && (button_space > 1))
+ {
+ gtk_widget_hide (widget);
+ /*We can go a little wider for empty buttons*/
+ gtk_widget_set_size_request (button, tasklist_width / real_buttons * 0.9, -1);
+ if (task)
+ gtk_widget_hide (task->label);
+
+ }
+ else
+ {
+ gtk_widget_show (widget);
+ }
+ }
+ contents = contents->next;
+ }
+ children = children->next;
+ }
+ return;
+}
+
+static void
foreign_toplevel_handle_closed (void *data,
struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
ToplevelTask *task = data;
+
if (task->button)
+ {
+ GtkOrientation orient;
+ GtkWidget *outer_box, *parent_box;
+ int real_buttons, button_space;
+
+ outer_box = gtk_widget_get_parent (GTK_WIDGET (task->button));
gtk_widget_destroy (task->button);
-}
+ buttons = buttons -1;
+
+ if (tasklist_invocations > 1)
+ real_buttons = buttons / 2;
+
+ else
+ real_buttons = buttons;
+
+ if (real_buttons == 0)
+ return;
+
+ /* We don't need to modify button size on a vertical panel*/
+ orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (outer_box));
+ if (orient == GTK_ORIENTATION_VERTICAL)
+ return;
+ /*Get the box the tasklist outer box sits in
+ *and leave a little space so the buttons don't push other applets off the panel
+ */
+
+ parent_box = gtk_widget_get_ancestor ((outer_box), GTK_TYPE_BOX);
+ tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width) ;
+ button_space = (tasklist_width / real_buttons) * 0.75;
+ button_space = MIN(button_space, full_button_width);
+ adjust_buttons (GTK_CONTAINER(outer_box), button_space, real_buttons, NULL);
+ }
+}
static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = {
.title = foreign_toplevel_handle_title,
@@ -286,6 +542,7 @@ toplevel_task_disconnected_from_widget (ToplevelTask *task)
struct zwlr_foreign_toplevel_handle_v1 *toplevel = task->toplevel;
task->button = NULL;
+ task->icon = NULL;
task->label = NULL;
task->toplevel = NULL;
@@ -295,6 +552,34 @@ toplevel_task_disconnected_from_widget (ToplevelTask *task)
g_free (task);
}
+/*We have to use the "activate" signal here
+ *as only signals valid for GtkButton work,
+ *"clicked" is taken, and we need to separate
+ *showing the desktop from mouse clicks on window buttons
+ */
+void
+toggle_show_desktop(GtkWidget *button, gboolean desktop_showing)
+{
+ window_hidden = desktop_showing;
+ g_signal_emit_by_name (button, "activate");
+}
+
+static void
+toggle_window(GtkButton *button, ToplevelTask *task)
+{
+ if (task->toplevel)
+ {
+ if (window_hidden)
+ {
+ zwlr_foreign_toplevel_handle_v1_set_minimized (task->toplevel);
+ }
+ else
+ {
+ zwlr_foreign_toplevel_handle_v1_unset_minimized (task->toplevel);
+ }
+ }
+}
+
static void
toplevel_task_handle_clicked (GtkButton *button, ToplevelTask *task)
{
@@ -314,13 +599,25 @@ toplevel_task_handle_clicked (GtkButton *button, ToplevelTask *task)
}
}
-static gboolean on_toplevel_button_press (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+static gboolean on_toplevel_button_press (GtkWidget *button, GdkEvent *event, TasklistManager *tasklist)
{
/* Assume event is a button press */
if (((GdkEventButton*)event)->button == GDK_BUTTON_SECONDARY)
{
- /* Returning true for secondary clicks suppresses the applet's default context menu,
- * which we do not want to show up for task buttons */
+ ContextMenu *menu = tasklist->context_menu;
+ ToplevelTask *task = g_object_get_data (G_OBJECT (button), toplevel_task_key);
+
+ g_object_set_data (G_OBJECT (menu->maximize), toplevel_task_key, task);
+ g_object_set_data (G_OBJECT (menu->minimize), toplevel_task_key, task);
+ g_object_set_data (G_OBJECT (menu->close), toplevel_task_key, task);
+
+ gtk_menu_item_set_label (GTK_MENU_ITEM (menu->minimize),
+ task->minimized ? "Unminimize" : "Minimize");
+ gtk_menu_item_set_label (GTK_MENU_ITEM (menu->maximize),
+ task->maximized ? "Unmaximize" : "Maximize");
+
+ gtk_menu_popup_at_widget (GTK_MENU (menu->menu), button,
+ GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_SOUTH_WEST, event);
return TRUE;
}
else
@@ -333,16 +630,29 @@ static ToplevelTask *
toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
ToplevelTask *task = g_new0 (ToplevelTask, 1);
+ GtkOrientation orient;
+ GtkWidget *whole_panel_box, *parent_box;
+ int real_buttons, button_space, panel_width;
+ buttons = buttons + 1;
+ orient = gtk_orientable_get_orientation (GTK_ORIENTABLE (tasklist->outer_box));
task->button = gtk_button_new ();
g_signal_connect (task->button, "clicked", G_CALLBACK (toplevel_task_handle_clicked), task);
+ g_signal_connect (task->button, "activate", G_CALLBACK (toggle_window), task);
+
+ task->icon = gtk_image_new_from_icon_name ("unknown", icon_size);
task->label = gtk_label_new ("");
- gtk_label_set_max_width_chars (GTK_LABEL (task->label), 1);
- gtk_widget_set_size_request (task->label, window_button_width, -1);
+ gtk_label_set_max_width_chars (GTK_LABEL (task->label), TASKLIST_TEXT_MAX_WIDTH);
gtk_label_set_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END);
- gtk_container_add (GTK_CONTAINER(task->button), task->label);
+ gtk_label_set_xalign (GTK_LABEL (task->label), 0.0);
+
+ GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start (GTK_BOX (box), task->icon, FALSE, FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (box), task->label, TRUE, TRUE, 2);
+ gtk_container_add (GTK_CONTAINER (task->button), box);
+ gtk_widget_set_name (task->button , "tasklist-button");
gtk_widget_show_all (task->button);
task->toplevel = toplevel;
@@ -354,11 +664,83 @@ toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handl
task,
(GDestroyNotify)toplevel_task_disconnected_from_widget);
- g_signal_connect (G_OBJECT (task->button),
- "button-press-event",
+ g_signal_connect (task->button, "button-press-event",
G_CALLBACK (on_toplevel_button_press),
- task);
+ tasklist);
+
+ /* Buttons on a vertical panel are not affected by how many are needed
+ * GTK handles compressing contents as needed as the window width tells
+ * GTK how much space to allocate the label and icon. Buttons will use
+ * the full width of a vertical panel without any special attention
+ * so break out here instead of breaking the vertical panel case
+ */
+
+ if (orient == GTK_ORIENTATION_VERTICAL)
+ return task;
+
+ /* On horizontal panels, GTK does not by default limit the width of the tasklist
+ * as it does not run out of space in the window until the entire panel is used,
+ * leaving buttons at full width until then and overflowing all other applets
+ *
+ * Thus we must get the tasklist's allocated width when extra space remains,
+ * which will be most of the distance between the handle and the next applet
+ * From there, we can expand buttons and/or hide elements as needed
+ * For some reason this function always gets called twice, so use half the value of buttons
+ * but do not attempt to adjust the global value as it would get adjusted twice
+ * Since we are adding a button here the true value cannot be zero
+ */
+ whole_panel_box = gtk_widget_get_toplevel(GTK_WIDGET (tasklist->outer_box));
+ parent_box = gtk_widget_get_ancestor(GTK_WIDGET (tasklist->outer_box), GTK_TYPE_BOX);
+ if (gtk_widget_get_allocated_width (parent_box) > 1)
+ {
+ tasklist_width = gtk_widget_get_allocated_width (parent_box);
+ }
+ else
+ {
+ tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width);
+ }
+
+ panel_width = gtk_widget_get_allocated_width (whole_panel_box);
+ /*on startup we get an allocated with of zero, so start with 1/3 the panel width
+ *as a sane default
+ *This may overflow on very crowded panels where the tasklist is less than 1/3ed the
+ *panel witth but will self-correct on opening or closing a few windows
+ */
+
+ if (tasklist_width <= 2)
+ tasklist_width = panel_width / 3;
+
+ if (tasklist_invocations > 1)
+ real_buttons = MAX ((buttons / 2), 1);
+
+ else
+ real_buttons = MAX ((buttons), 1);
+
+ /*always allow at least three buttons to fit without adjustment
+ *so short window lists don't overflow
+ */
+ if (tasklist_width > 0)
+ {
+ full_button_width = MIN(max_button_width, tasklist_width / 3);
+ }
+
+ /*Leave a little space so the buttons don't push other applets off the panel*/
+ button_space = (tasklist_width / real_buttons) * 0.75;
+ button_space = MIN(button_space, full_button_width);
+
+ /* iterate over all the buttons*/
+ adjust_buttons (GTK_CONTAINER (tasklist->list), button_space, real_buttons, task);
+
+ /*Reset the tasklist width after button adjustments*/
+ if (gtk_widget_get_allocated_width (parent_box) > 1)
+ {
+ tasklist_width = gtk_widget_get_allocated_width (parent_box);
+ }
+ else
+ {
+ tasklist_width = MAX(gtk_widget_get_allocated_width (parent_box), tasklist_width);
+ }
return task;
}
@@ -367,6 +749,8 @@ wayland_tasklist_new ()
{
wayland_tasklist_init_if_needed ();
TasklistManager *tasklist = tasklist_manager_new ();
+
+ tasklist_invocations = tasklist_invocations + 1;
if (!tasklist)
return gtk_label_new ("Shell does not support WLR Foreign Toplevel Control");
return tasklist->outer_box;