/* Wncklet applet Wayland backend */

/*
 * Copyright (C) 2019 William Wold
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include <config.h>

#ifndef HAVE_WAYLAND
#error file should only be compiled when HAVE_WAYLAND is enabled
#endif

#include <gdk/gdkwayland.h>
#include <gio/gdesktopappinfo.h>

#include "wayland-backend.h"
#include "wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h"

/*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;

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 const char *tasklist_manager_key = "tasklist_manager";
static const char *toplevel_task_key = "toplevel_task";

static gboolean has_initialized = FALSE;
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 ToplevelTask *toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *handle);

guint buttons, tasklist_width;

static void
wl_registry_handle_global (void *_data,
			   struct wl_registry *registry,
			   uint32_t id,
			   const char *interface,
			   uint32_t version)
{
	/* pull out needed globals */
	if (strcmp (interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0)
	{
		g_warn_if_fail (zwlr_foreign_toplevel_manager_v1_interface.version == 2);
		foreign_toplevel_manager_global_id = id;
		foreign_toplevel_manager_global_version =
			MIN((uint32_t)zwlr_foreign_toplevel_manager_v1_interface.version, version);
	}
}

static void
wl_registry_handle_global_remove (void *_data,
				  struct wl_registry *_registry,
				  uint32_t id)
{
	if (id == foreign_toplevel_manager_global_id)
	{
		foreign_toplevel_manager_global_id = 0;
	}
}

static const struct wl_registry_listener wl_registry_listener = {
    .global = wl_registry_handle_global,
    .global_remove = wl_registry_handle_global_remove,
};

static void
wayland_tasklist_init_if_needed (void)
{
	if (has_initialized)
		return;

	GdkDisplay *gdk_display = gdk_display_get_default ();
	g_return_if_fail (gdk_display);
	g_return_if_fail (GDK_IS_WAYLAND_DISPLAY (gdk_display));

	struct wl_display *wl_display = gdk_wayland_display_get_wl_display (gdk_display);
	wl_registry_global = wl_display_get_registry (wl_display);
	wl_registry_add_listener (wl_registry_global, &wl_registry_listener, NULL);
	wl_display_roundtrip (wl_display);

	if (!foreign_toplevel_manager_global_id)
		g_warning ("%s not supported by Wayland compositor",
			   zwlr_foreign_toplevel_manager_v1_interface.name);

	has_initialized = TRUE;
}

static void
foreign_toplevel_manager_handle_toplevel (void *data,
					  struct zwlr_foreign_toplevel_manager_v1 *manager,
					  struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
	TasklistManager *tasklist = data;
	ToplevelTask *task = toplevel_task_new (tasklist, toplevel);
	gtk_box_pack_start (GTK_BOX (tasklist->list), task->button, TRUE, TRUE, 0);
}

static void
foreign_toplevel_manager_handle_finished (void *data,
					  struct zwlr_foreign_toplevel_manager_v1 *manager)
{
	TasklistManager *tasklist = data;

	tasklist->manager = NULL;
	zwlr_foreign_toplevel_manager_v1_destroy (manager);

	if (tasklist->outer_box)
		g_object_set_data (G_OBJECT (tasklist->outer_box),
				   tasklist_manager_key,
				   NULL);

	g_free (tasklist);
}

static const struct zwlr_foreign_toplevel_manager_v1_listener foreign_toplevel_manager_listener = {
	.toplevel = foreign_toplevel_manager_handle_toplevel,
	.finished = foreign_toplevel_manager_handle_finished,
};

static void
tasklist_manager_disconnected_from_widget (TasklistManager *tasklist)
{
	if (tasklist->list)
	{
		GList *children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
		for (GList *iter = children; iter != NULL; iter = g_list_next (iter))
			gtk_widget_destroy (GTK_WIDGET (iter->data));
		g_list_free(children);
		tasklist->list = NULL;
	}

	if (tasklist->outer_box)
		tasklist->outer_box = NULL;

	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 (void)
{
	if (!foreign_toplevel_manager_global_id)
		return NULL;

	TasklistManager *tasklist = g_new0 (TasklistManager, 1);
	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);
	gtk_widget_show (tasklist->list);
	tasklist->manager = wl_registry_bind (wl_registry_global,
					     foreign_toplevel_manager_global_id,
					     &zwlr_foreign_toplevel_manager_v1_interface,
					     foreign_toplevel_manager_global_version);
	zwlr_foreign_toplevel_manager_v1_add_listener (tasklist->manager,
						       &foreign_toplevel_manager_listener,
						       tasklist);
	g_object_set_data_full (G_OBJECT (tasklist->outer_box),
				tasklist_manager_key,
				tasklist,
				(GDestroyNotify)tasklist_manager_disconnected_from_widget);
	tasklist->context_menu = context_menu_new ();
	return tasklist;
}

static void
foreign_toplevel_handle_title (void *data,
			       struct zwlr_foreign_toplevel_handle_v1 *toplevel,
			       const char *title)
{
	ToplevelTask *task = data;

	if (task->label)
	{
		gtk_label_set_label (GTK_LABEL (task->label), title);
	}
}

static void
foreign_toplevel_handle_app_id (void *data,
				struct zwlr_foreign_toplevel_handle_v1 *toplevel,
				const char *app_id)
{
	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
foreign_toplevel_handle_output_enter (void *data,
				      struct zwlr_foreign_toplevel_handle_v1 *toplevel,
				      struct wl_output *output)
{
	/* ignore */
}

static void
foreign_toplevel_handle_output_leave (void *data,
				      struct zwlr_foreign_toplevel_handle_v1 *toplevel,
				      struct wl_output *output)
{
	/* ignore */
}

static void
foreign_toplevel_handle_state (void *data,
			       struct zwlr_foreign_toplevel_handle_v1 *toplevel,
			       struct wl_array *state)
{
	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)
	{
		switch (*i)
		{
		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;
		}
	}

	gtk_button_set_relief (GTK_BUTTON (task->button), task->active ? GTK_RELIEF_NORMAL : GTK_RELIEF_NONE);
}

static void
foreign_toplevel_handle_done (void *data,
			      struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
	/* ignore */
}

static void
foreign_toplevel_handle_closed (void *data,
				struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
	ToplevelTask *task = data;

	if (task->button)
	{
		GtkOrientation orient;
		GtkWidget *button;
		GtkWidget  *box;
		GtkWidget *outer_box = gtk_widget_get_parent (GTK_WIDGET (task->button));
		gtk_widget_destroy (task->button);
		buttons = buttons -1;

		if (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;

		/* Horizontal panel: if buttons can now fit
		 * with both labels and icons show them
		 */
		if (tasklist_width / buttons > icon_size * 3)
		{
			GList* children = gtk_container_get_children (GTK_CONTAINER (outer_box));
			while (children != NULL)
			{
				button = GTK_WIDGET (children->data);

				/* If maximum width buttons fix, expand to that dimension*/
				if (buttons * max_button_width < tasklist_width)
					gtk_widget_set_size_request (button, max_button_width, -1);

				/* Otherwise expand remaining buttons to fill the tasklist*/
				else
					gtk_widget_set_size_request (button, tasklist_width / buttons, -1);

				gtk_widget_show_all (button);
				children = children->next;
			}
		}
		/* If buttons with icons will fit, bring them back*/
		else if (tasklist_width / buttons > icon_size * 2)
		{
			GtkWidget *widget;
			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));
				GList* contents = gtk_container_get_children (GTK_CONTAINER (box));
				while (contents != NULL)
				{
					widget = GTK_WIDGET (contents->data);
					if (GTK_IS_LABEL (widget))
						gtk_widget_hide (widget);

					if (GTK_IS_IMAGE (widget))
						gtk_widget_show (widget);

					contents = contents->next;
					gtk_widget_show (box);
					gtk_widget_show (button);
				}

				children = children->next;
				gtk_widget_set_size_request (button, tasklist_width / buttons, -1);
			}
		}
		/* If we still cannot fit labels or icons, just fill the available space*/
		else
		{
			GList* children = gtk_container_get_children (GTK_CONTAINER (outer_box));
			while (children != NULL)
			{
				button = GTK_WIDGET (children->data);
				gtk_widget_set_size_request (button, tasklist_width / buttons, -1);
				children = children->next;
			}
		}
	}
}

static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = {
	.title = foreign_toplevel_handle_title,
	.app_id = foreign_toplevel_handle_app_id,
	.output_enter = foreign_toplevel_handle_output_enter,
	.output_leave = foreign_toplevel_handle_output_leave,
	.state = foreign_toplevel_handle_state,
	.done = foreign_toplevel_handle_done,
	.closed = foreign_toplevel_handle_closed,
};

static void
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;

	if (toplevel)
		zwlr_foreign_toplevel_handle_v1_destroy (toplevel);

	g_free (task);
}

static void
toplevel_task_handle_clicked (GtkButton *button, ToplevelTask *task)
{
	if (task->toplevel)
	{
		if (task->active)
		{
			zwlr_foreign_toplevel_handle_v1_set_minimized (task->toplevel);
		}
		else
		{
			GdkDisplay *gdk_display = gtk_widget_get_display (GTK_WIDGET (button));
			GdkSeat *gdk_seat = gdk_display_get_default_seat (gdk_display);
			struct wl_seat *wl_seat = gdk_wayland_seat_get_wl_seat (gdk_seat);
			zwlr_foreign_toplevel_handle_v1_activate (task->toplevel, wl_seat);
		}
	}
}

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)
	{
		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
	{
		return FALSE;
	}
}

static ToplevelTask *
toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *toplevel)
{
	ToplevelTask *task = g_new0 (ToplevelTask, 1);
	GtkWidget *button;
	GtkOrientation orient;

	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);

	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), TASKLIST_TEXT_MAX_WIDTH);
	gtk_label_set_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END);
	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);

	/* 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)
	{
		gtk_widget_show_all (task->button);
		task->toplevel = toplevel;
		zwlr_foreign_toplevel_handle_v1_add_listener (toplevel,
							      &foreign_toplevel_handle_listener,
							      task);
		g_object_set_data_full (G_OBJECT (task->button),
					toplevel_task_key,
					task,
					(GDestroyNotify)toplevel_task_disconnected_from_widget);

	g_signal_connect (task->button, "button-press-event",
			  G_CALLBACK (on_toplevel_button_press),
			  tasklist);

	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
	 */


	tasklist_width = gtk_widget_get_allocated_width (GTK_WIDGET (tasklist->outer_box));

	/* First button can be buggy with this so hardcode it to expand to the limit */
	if (buttons == 1)
		gtk_widget_set_size_request (task->button, max_button_width, -1);

	/* if the number of buttons forces width to less than 3x the icon size, shrink them  */
	if ((buttons != 0) && (tasklist_width > 1 )&& (tasklist_width / buttons < (icon_size * 3)))
	{
		/* adjust the current button first or it can be missed */
		if (tasklist_width / buttons > icon_size * 2)
		{
			gtk_widget_hide (task->label);
			gtk_widget_show (task->icon);
		}
		else
		{
			gtk_widget_show (task->label);
			gtk_widget_hide (task->icon);
		}
		gtk_widget_show (box);
		gtk_widget_show (task->button);

		/* iterate over all the buttons, first hide labels
		 * then hide icons and bring back labels
		 */
		GtkWidget *widget;

		GList* children = gtk_container_get_children (GTK_CONTAINER (tasklist->list));
		while (children != NULL)
		{
			button = GTK_WIDGET (children->data);
			box = gtk_bin_get_child (GTK_BIN (button));

			/* hide labels of all buttons but show icons if only icons will fit */
			if (tasklist_width / buttons > icon_size * 2)
			{
				/* find the icon and the label, show just the icon */
				GList* contents = gtk_container_get_children (GTK_CONTAINER (box));

				while (contents != NULL)
				{
					widget = GTK_WIDGET (contents->data);
					if (GTK_IS_LABEL (widget))
						gtk_widget_hide (widget);

					if (GTK_IS_IMAGE (widget))
						gtk_widget_show (widget);

					contents = contents->next;
				}
			}
			else
			{
				/* find the icon and the label, show just the label as it is more
				 * compressable than the icon. Though less meaningful at this size,
				 * it is enough to keep the tasklist from disappearing on themes
				 * that do not set borders around tasklist buttons.
				 * This is same behavior as on x11 save that an extreme number of
				 * buttons (50+ on 700px of space) can still overflow
				 */

				GList* contents = gtk_container_get_children (GTK_CONTAINER (box));
				while (contents != NULL)
				{
					widget = GTK_WIDGET (contents->data);
					if (GTK_IS_LABEL (widget))
						gtk_widget_show (widget);

					if (GTK_IS_IMAGE (widget))
						gtk_widget_hide (widget);

					contents = contents->next;
				}
			}
			/*expand buttons with labels or everything hidden to fit remaining space*/
			gtk_widget_set_size_request (button, tasklist_width / buttons, -1);
			/*show the button and any contents that fit, then get the next button*/
			gtk_widget_show (box);
			gtk_widget_show (button);

			children = children->next;
		}
	}
	else
	{
		GList* children = gtk_container_get_children (GTK_CONTAINER(tasklist->list));
		while (children != NULL)
		{
			button = GTK_WIDGET (children->data);
			if (((buttons ) * max_button_width < tasklist_width) || (buttons == 1))

				/*Don't let buttons expand over the maximum button size*/
				gtk_widget_set_size_request (button, max_button_width, -1);

			else
				/*if full width buttons won't fit, size them to just fill the tasklist*/
				gtk_widget_set_size_request (button, tasklist_width / buttons, -1);

			children = children->next;
		}
	gtk_widget_show_all (task->button);
	}

	/*Reset the tasklist width after button adjustments*/
	tasklist_width = gtk_widget_get_allocated_width (GTK_WIDGET (tasklist->outer_box));

	task->toplevel = toplevel;
	zwlr_foreign_toplevel_handle_v1_add_listener (toplevel,
						      &foreign_toplevel_handle_listener,
						      task);
	g_object_set_data_full (G_OBJECT (task->button),
				toplevel_task_key,
				task,
				(GDestroyNotify)toplevel_task_disconnected_from_widget);

	g_signal_connect (task->button, "button-press-event",
			  G_CALLBACK (on_toplevel_button_press),
			  tasklist);

	return task;
}

GtkWidget*
wayland_tasklist_new ()
{
	wayland_tasklist_init_if_needed ();
	TasklistManager *tasklist = tasklist_manager_new ();
	if (!tasklist)
		return gtk_label_new ("Shell does not support WLR Foreign Toplevel Control");
	return tasklist->outer_box;
}

static TasklistManager *
tasklist_widget_get_tasklist (GtkWidget* tasklist_widget)
{
	return g_object_get_data (G_OBJECT (tasklist_widget), tasklist_manager_key);
}

void
wayland_tasklist_set_orientation (GtkWidget* tasklist_widget, GtkOrientation orient)
{
	TasklistManager *tasklist = tasklist_widget_get_tasklist (tasklist_widget);
	g_return_if_fail(tasklist);
	gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->list), orient);
	gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->outer_box), orient);
}