From ea5f814e611094cca992afb6d54b49d178bce959 Mon Sep 17 00:00:00 2001 From: William Wold Date: Wed, 30 Sep 2020 16:12:15 -0700 Subject: Window list Wayland support --- applets/wncklet/Makefile.am | 8 + ...e.panel.Wncklet.mate-panel-applet.desktop.in.in | 2 +- applets/wncklet/wayland-backend.c | 366 +++++++++++++++++++++ applets/wncklet/wayland-backend.h | 48 +++ applets/wncklet/window-list.c | 10 +- 5 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 applets/wncklet/wayland-backend.c create mode 100644 applets/wncklet/wayland-backend.h diff --git a/applets/wncklet/Makefile.am b/applets/wncklet/Makefile.am index b933f81b..e6844963 100644 --- a/applets/wncklet/Makefile.am +++ b/applets/wncklet/Makefile.am @@ -23,6 +23,14 @@ WNCKLET_SOURCES = \ showdesktop.h \ $(BUILT_SOURCES) +if ENABLE_WAYLAND +WNCKLET_SOURCES += \ + wayland-backend.c \ + wayland-backend.h \ + wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-code.c \ + wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h +endif + WNCKLET_LDADD = \ ../../libmate-panel-applet/libmate-panel-applet-4.la \ $(WNCKLET_LIBS) \ diff --git a/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in b/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in index 490edcc1..99e7815c 100644 --- a/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in +++ b/applets/wncklet/org.mate.panel.Wncklet.mate-panel-applet.desktop.in.in @@ -37,7 +37,7 @@ Description=Switch between open windows using buttons # Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=mate-panel-window-list MateComponentId=OAFIID:MATE_TasklistApplet;OAFIID:MATE_WindowListApplet; -Platforms=X11; +Platforms=X11;Wayland; X-MATE-Bugzilla-Bugzilla=MATE X-MATE-Bugzilla-Product=mate-panel X-MATE-Bugzilla-Component=window list diff --git a/applets/wncklet/wayland-backend.c b/applets/wncklet/wayland-backend.c new file mode 100644 index 00000000..d51fb7d0 --- /dev/null +++ b/applets/wncklet/wayland-backend.c @@ -0,0 +1,366 @@ +/* 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 + +#ifndef HAVE_WAYLAND +#error file should only be compiled when HAVE_WAYLAND is enabled +#endif + +#include + +#include "wayland-backend.h" +#include "wayland-protocol/wlr-foreign-toplevel-management-unstable-v1-client.h" + +static const int window_button_width = 140; + +typedef struct +{ + GtkWidget *list; + GtkWidget *outer_box; + struct zwlr_foreign_toplevel_manager_v1 *manager; +} TasklistManager; + +typedef struct +{ + GtkWidget *button; + GtkWidget *label; + struct zwlr_foreign_toplevel_handle_v1 *toplevel; + gboolean active; +} 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 TasklistManager *tasklist_manager_new (); +static ToplevelTask *toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *handle); + +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 () +{ + 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, 2); +} + +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); +} + +static TasklistManager * +tasklist_manager_new () +{ + if (!foreign_toplevel_manager_global_id) + return NULL; + + TasklistManager *tasklist = g_new0 (TasklistManager, 1); + tasklist->list = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + 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); + 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) +{ + // ignore +} + +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; + + 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; + + 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) + gtk_widget_destroy (task->button); +} + + +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->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 ToplevelTask * +toplevel_task_new (TasklistManager *tasklist, struct zwlr_foreign_toplevel_handle_v1 *toplevel) +{ + ToplevelTask *task = g_new0 (ToplevelTask, 1); + + task->button = gtk_button_new (); + g_signal_connect (task->button, "clicked", G_CALLBACK (toplevel_task_handle_clicked), task); + + 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_ellipsize (GTK_LABEL (task->label), PANGO_ELLIPSIZE_END); + gtk_container_add (GTK_CONTAINER(task->button), task->label); + + 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); + 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); + gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->list), orient); + gtk_orientable_set_orientation (GTK_ORIENTABLE (tasklist->outer_box), orient); +} diff --git a/applets/wncklet/wayland-backend.h b/applets/wncklet/wayland-backend.h new file mode 100644 index 00000000..d3ce93da --- /dev/null +++ b/applets/wncklet/wayland-backend.h @@ -0,0 +1,48 @@ +/* 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. + */ + +#ifndef _WNCKLET_APPLET_WAYLAND_BACKEND_H_ +#define _WNCKLET_APPLET_WAYLAND_BACKEND_H_ + +#ifdef PACKAGE_NAME // only check HAVE_WAYLAND if config.h has been included +#ifndef HAVE_WAYLAND +#error file should only be included when HAVE_WAYLAND is enabled +#endif +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +GtkWidget* wayland_tasklist_new (void); +void wayland_tasklist_set_orientation (GtkWidget* tasklist_widget, GtkOrientation orient); + + +#ifdef __cplusplus +} +#endif + +#endif // _WNCKLET_APPLET_WAYLAND_BACKEND_H_ + + diff --git a/applets/wncklet/window-list.c b/applets/wncklet/window-list.c index 54105f60..5c336259 100644 --- a/applets/wncklet/window-list.c +++ b/applets/wncklet/window-list.c @@ -27,6 +27,7 @@ #ifdef HAVE_WAYLAND #include +#include "wayland-backend.h" #endif // HAVE_WAYLAND #define MATE_DESKTOP_USE_UNSTABLE_API @@ -138,7 +139,12 @@ static void tasklist_apply_orientation(TasklistData* tasklist) } #endif // HAVE_X11 - // Not yet implemented for Wayland +#ifdef HAVE_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) + { + wayland_tasklist_set_orientation(tasklist->tasklist, tasklist->orientation); + } +#endif } static void tasklist_set_button_relief(TasklistData* tasklist, GtkReliefStyle relief) @@ -759,7 +765,7 @@ gboolean window_list_applet_fill(MatePanelApplet* applet) #ifdef HAVE_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) { - tasklist->tasklist = gtk_label_new ("[Tasklist not supported on Wayland]"); + tasklist->tasklist = wayland_tasklist_new(); } else #endif // HAVE_WAYLAND -- cgit v1.2.1