diff options
Diffstat (limited to 'applets/wncklet/wayland-backend.c')
| -rw-r--r-- | applets/wncklet/wayland-backend.c | 420 |
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; |
