diff --git a/.reuse/dep5 b/.reuse/dep5 index 73231446bc6e2f72d9d403558f35632b86304e90..1a0bfb592ac3aca65919cacfe47dced539f8eaa1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -28,6 +28,7 @@ License: LGPL-2.1-or-later Files: protocols/phoc-device-state-unstable-v1.xml protocols/phoc-layer-shell-effects-unstable-v1.xml + protocols/phoc-toplevel-addons.xml protocols/phosh-private.xml Copyright: See individual files License: MIT diff --git a/examples/meson.build b/examples/meson.build index 7009a1a194b836853dc7adbdf12312ca44029570..f660e0f111027245c7c777d2f5a20addcf4cca33 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -26,3 +26,9 @@ executable( sources: ['idle.c', client_protos_headers, protos_sources], dependencies: [glib, wayland_client], ) + +executable( + 'toplevel-addons', + sources: ['toplevel-addons.c', client_protos_headers, protos_sources], + dependencies: [glib, wayland_client], +) diff --git a/examples/toplevel-addons.c b/examples/toplevel-addons.c new file mode 100644 index 0000000000000000000000000000000000000000..16ef06d7863bdd9e55c5af3413893f69572459bd --- /dev/null +++ b/examples/toplevel-addons.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2025 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + * Author: Arun Mani J + */ + +#define G_LOG_DOMAIN "phoc-toplevel-addons-example" + +#include +#include +#include + + +typedef struct _Toplevel { + struct zwlr_foreign_toplevel_handle_v1 *handle; + char *app_id; + char *title; + gboolean new; +} Toplevel; + + +static struct wl_display *display; +static struct phoc_toplevel_addons *toplevel_addons; +static struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager; +static GHashTable *toplevels; + +static Toplevel * +toplevel_new (void) +{ + return g_new0 (Toplevel, 1); +} + + +static void +toplevel_destroy (Toplevel *toplevel) +{ + g_clear_pointer (&toplevel->title, g_free); + g_clear_pointer (&toplevel->app_id, g_free); + + g_free (toplevel); +} + + +static void +print_toplevel (Toplevel *toplevel) +{ + g_print ("Toplevel %p: title: %s, app_id: %s\n", + toplevel->handle, + toplevel->title, + toplevel->app_id); +} + + +static void +handle_state (void *data, + struct phoc_toplevel_addons *toplevel_addons_, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle, + struct wl_array *state) + + +{ + Toplevel *toplevel = g_hash_table_lookup (toplevels, toplevel_handle); + uint32_t *val; + gboolean responsive = TRUE; + + g_assert (toplevel); + + g_print ("State changed for toplevel %p - %s:\n", + toplevel, toplevel->title); + + wl_array_for_each (val, state) { + switch (*val) { + case PHOC_TOPLEVEL_ADDONS_STATE_UNRESPONSIVE: + responsive = FALSE; + break; + default: + g_print ("\tUnknown state %d", *val); + } + } + + if (responsive) + g_print ("\tResponsive"); + else + g_print ("\tUnresponsive"); + + g_print ("\n"); +} + + +static const struct phoc_toplevel_addons_listener toplevel_addons_listener = { + .state = handle_state, +}; + + +static void +handle_title (void *data, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle, + const char *title) +{ + Toplevel *toplevel = data; + + toplevel->title = g_strdup (title); +} + + +static void +handle_app_id (void *data, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle, + const char *app_id) +{ + Toplevel *toplevel = data; + + toplevel->app_id = g_strdup (app_id); +} + + +static void +handle_output_enter (void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_output *output) +{ +} + + +static void +handle_output_leave (void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_output *output) +{ +} + + +static void +handle_state_ (void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct wl_array *state) +{ +} + + +static void +handle_done (void *data, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle) +{ + Toplevel *toplevel = data; + + if (toplevel->new) + print_toplevel (toplevel); + toplevel->new = FALSE; +} + + +static void +handle_closed (void *data, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle) +{ + Toplevel *toplevel = g_hash_table_lookup (toplevels, toplevel_handle); + + g_assert (toplevel); + + g_print ("Toplevel %p - %s closed\n", toplevel, toplevel->title); + g_hash_table_remove (toplevels, toplevel_handle); +} + +static void +handle_parent (void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, + struct zwlr_foreign_toplevel_handle_v1 *parent) +{ +} + + +struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_listener = { + handle_title, + handle_app_id, + handle_output_enter, + handle_output_leave, + handle_state_, + handle_done, + handle_closed, + handle_parent, +}; + + +static void +handle_toplevel (void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager_, + struct zwlr_foreign_toplevel_handle_v1 *toplevel_handle) +{ + Toplevel *toplevel = toplevel_new (); + + g_print ("New toplevel: %p\n", toplevel_handle); + toplevel->handle = toplevel_handle; + toplevel->new = TRUE; + g_hash_table_insert (toplevels, toplevel->handle, toplevel); + + zwlr_foreign_toplevel_handle_v1_add_listener (toplevel->handle, + &toplevel_handle_listener, + toplevel); +} + + +static void +handle_finished (void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager_) +{ +} + + +struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_listener = { + .toplevel = handle_toplevel, + .finished = handle_finished, +}; + + +static void +handle_global (void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + if (g_str_equal (interface, phoc_toplevel_addons_interface.name)) { + toplevel_addons = wl_registry_bind (registry, name, + &phoc_toplevel_addons_interface, 1); + phoc_toplevel_addons_add_listener (toplevel_addons, + &toplevel_addons_listener, + NULL); + } else if (g_str_equal (interface, zwlr_foreign_toplevel_manager_v1_interface.name)) { + toplevel_manager = wl_registry_bind (registry, name, + &zwlr_foreign_toplevel_manager_v1_interface, 1); + zwlr_foreign_toplevel_manager_v1_add_listener (toplevel_manager, + &toplevel_manager_listener, + NULL); + } +} + + +static void +handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) +{ +} + + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + + +int +main (int argc, char **argv) +{ + struct wl_registry *registry; + + display = wl_display_connect (NULL); + if (display == NULL) { + g_critical ("Failed to create display"); + return 1; + } + + toplevels = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)toplevel_destroy); + registry = wl_display_get_registry (display); + wl_registry_add_listener (registry, ®istry_listener, NULL); + wl_display_roundtrip (display); + + if (toplevel_addons == NULL) { + g_critical ("phosh_toplevel_addons not available"); + return 1; + } + + g_message ("Press CTRL-C to quit"); + wl_display_roundtrip (display); + + while (wl_display_dispatch (display)) { + // This space intentionally left blank + } + + return 0; +} diff --git a/protocols/meson.build b/protocols/meson.build index 6ef0be5d34cfe153758064f9f383a7a7c187cb91..8cbfa8f1c29a5e9e40d01d8e4009029229d12c4b 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -16,6 +16,7 @@ wayland_protocols = [ ['gtk-shell.xml'], ['phoc-device-state-unstable-v1.xml'], ['phoc-layer-shell-effects-unstable-v1.xml'], + ['phoc-toplevel-addons.xml'], ['phosh-private.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], diff --git a/protocols/phoc-toplevel-addons.xml b/protocols/phoc-toplevel-addons.xml new file mode 100644 index 0000000000000000000000000000000000000000..e4629608c0dc38043dbb539a33ccbba52ee7c1b9 --- /dev/null +++ b/protocols/phoc-toplevel-addons.xml @@ -0,0 +1,52 @@ + + + + Copyright © 2025 The Phosh Developers + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + Phoc Toplevel Addons is a private protocol to provide additional information about a toplevel + from Phoc. This is either because we want to validate the protocol bits (e.g. for PID) or the + corresponding upstream protocols (like ext_foreign_toplevel_state_v1). + + + + Force close the toplevel. This must be used as a last resort to close an unresponsive + toplevel. + + + + + + The different states of a toplevel as deemed by compositor. + + + + + + + + + + diff --git a/src/desktop.c b/src/desktop.c index 8dc79937c49078a5891ff0454c9ed468ac7dee86..29d566dce5dd563f475c7876d9d40498d93bd0d5 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -107,6 +107,7 @@ typedef struct _PhocDesktopPrivate { /* Protocols without upstreamable implementations */ PhocPhoshPrivate *phosh; PhocGtkShell *gtk_shell; + PhocToplevelAddons *toplevel_addons; /* Protocols that should go upstream */ PhocLayerShellEffects *layer_shell_effects; @@ -670,6 +671,7 @@ phoc_desktop_constructed (GObject *object) priv->gtk_shell = phoc_gtk_shell_create (self, wl_display); priv->phosh = phoc_phosh_private_new (); + priv->toplevel_addons = phoc_toplevel_addons_new (); self->xdg_activation_v1 = wlr_xdg_activation_v1_create (wl_display); self->xdg_activation_v1_request_activate.notify = phoc_xdg_activation_v1_handle_request_activate; @@ -1216,6 +1218,26 @@ phoc_desktop_get_phosh_private (PhocDesktop *self) return priv->phosh; } +/** + * phoc_desktop_get_toplevel_addons: + * @self: The `PhocDesktop` + * + * Gets a handler of the phoc_toplevel_addons_unstable_v1 protocol implementation. + * + * Returns: (transfer none): phoc_toplevel_addons_unstable_v1 implementation handler. + */ +PhocToplevelAddons * +phoc_desktop_get_toplevel_addons (PhocDesktop *self) +{ + PhocDesktopPrivate *priv; + + g_assert (PHOC_IS_DESKTOP (self)); + + priv = phoc_desktop_get_instance_private (self); + + return priv->toplevel_addons; +} + void phoc_desktop_notify_activity (PhocDesktop *self, PhocSeat *seat) { diff --git a/src/desktop.h b/src/desktop.h index cd4152223f35bc013e5b62e39026479f31d69cab..e9b10cc810012f1a424d3e6930ac788abc0c6cf9 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -4,6 +4,7 @@ #include "gtk-shell.h" #include "layer-shell-effects.h" #include "phosh-private.h" +#include "toplevel-addons.h" #include "view.h" #include "workspace-manager.h" #include "xwayland-surface.h" @@ -168,6 +169,7 @@ GSList * phoc_desktop_get_layer_surface_stacks (PhocDesktop *s PhocGtkShell * phoc_desktop_get_gtk_shell (PhocDesktop *self); PhocPhoshPrivate * phoc_desktop_get_phosh_private (PhocDesktop *self); +PhocToplevelAddons * phoc_desktop_get_toplevel_addons (PhocDesktop *self); void phoc_desktop_notify_activity (PhocDesktop *self, PhocSeat *seat); diff --git a/src/meson.build b/src/meson.build index de1221e2b858ec605eca5e79513a718c26d079a5..585363e56718f1f9dff907539dbc50bae0f4bcaf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -110,6 +110,8 @@ sources = files( 'switch.h', 'tablet.c', 'tablet.h', + 'toplevel-addons.c', + 'toplevel-addons.h', 'touch-point.c', 'touch-point.h', 'touch.c', diff --git a/src/toplevel-addons.c b/src/toplevel-addons.c new file mode 100644 index 0000000000000000000000000000000000000000..c84292763e292ee5f974cd70582c7ba04e6f1def --- /dev/null +++ b/src/toplevel-addons.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2025 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + * Author: Arun Mani J + */ + +#define G_LOG_DOMAIN "phoc-toplevel-addons" + +#include "phoc-config.h" +#include "server.h" +#include "toplevel-addons.h" +#include + +/** + * PhocToplevelAddons: + * + * Additional information about a toplevel from Phoc. + */ + +struct _PhocToplevelAddons { + GObject *parent; + + guint32 version; + struct wl_resource *resource; + struct wl_global *global; +}; + +G_DEFINE_TYPE (PhocToplevelAddons, phoc_toplevel_addons, G_TYPE_OBJECT); + +#define TOPLEVEL_ADDONS_VERSION 1 + + +static void +toplevel_addons_handle_resource_destroy (struct wl_resource *resource) +{ + PhocToplevelAddons *self = wl_resource_get_user_data (resource); + + g_debug ("Destroying resource %p", resource); + self->resource = NULL; +} + + +static void +handle_force_close (struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *toplevel) +{ + PhocToplevelAddons *self = wl_resource_get_user_data (resource); + struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; + PhocView *view; + + g_assert (PHOC_IS_TOPLEVEL_ADDONS (self)); + + toplevel_handle = wl_resource_get_user_data (toplevel); + view = toplevel_handle->data; + + if (!PHOC_IS_VIEW (view)) { + g_warning ("Toplevel handle does not have a view: %p", toplevel_handle); + return; + } + + phoc_view_force_close (view); +} + + +static const struct phoc_toplevel_addons_interface toplevel_addons_impl = { + handle_force_close, +}; + +static void +toplevel_addons_bind (struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + PhocToplevelAddons *self = data; + struct wl_resource *resource = wl_resource_create (client, + &phoc_toplevel_addons_interface, + version, id); + + if (self->resource) { + wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "Only a single client can bind to phoc_toplevel_addons protocol"); + return; + } + + g_debug ("Binding client %d with version %d", id, version); + wl_resource_set_implementation (resource, + &toplevel_addons_impl, + self, + toplevel_addons_handle_resource_destroy); + self->resource = resource; + self->version = version; +} + + +static void +phoc_toplevel_addons_constructed (GObject *object) +{ + PhocToplevelAddons *self = PHOC_TOPLEVEL_ADDONS (object); + struct wl_display *wl_display = phoc_server_get_wl_display (phoc_server_get_default ()); + + G_OBJECT_CLASS (phoc_toplevel_addons_parent_class)->constructed (object); + + g_info ("Initialzing toplevel_addons interface"); + self->global = wl_global_create (wl_display, &phoc_toplevel_addons_interface, + TOPLEVEL_ADDONS_VERSION, self, toplevel_addons_bind); +} + + +static void +phoc_toplevel_addons_finalize (GObject *object) +{ + PhocToplevelAddons *self = PHOC_TOPLEVEL_ADDONS (object); + + wl_global_destroy (self->global); + + G_OBJECT_CLASS (phoc_toplevel_addons_parent_class)->finalize (object); +} + + +static void +phoc_toplevel_addons_class_init (PhocToplevelAddonsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = phoc_toplevel_addons_constructed; + object_class->finalize = phoc_toplevel_addons_finalize; +} + + +static void +phoc_toplevel_addons_init (PhocToplevelAddons *self) +{ +} + + +PhocToplevelAddons * +phoc_toplevel_addons_new (void) +{ + return g_object_new (PHOC_TYPE_TOPLEVEL_ADDONS, NULL); +} + + +void +phoc_toplevel_addons_notify_state (PhocToplevelAddons *self, + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_array *state) +{ + struct wl_client *client; + struct wl_resource *resource; + + g_assert (PHOC_IS_TOPLEVEL_ADDONS (self)); + + if (!self->resource) + return; + + if (self->version < 1) + return; + + client = wl_resource_get_client (self->resource); + + wl_resource_for_each (resource, &toplevel->resources) + { + if (client != wl_resource_get_client (resource)) + continue; + + phoc_toplevel_addons_send_state (self->resource, resource, state); + } +} diff --git a/src/toplevel-addons.h b/src/toplevel-addons.h new file mode 100644 index 0000000000000000000000000000000000000000..b3bdc0c96c70a2bac003c441a5b8857f521424fa --- /dev/null +++ b/src/toplevel-addons.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define PHOC_TYPE_TOPLEVEL_ADDONS phoc_toplevel_addons_get_type () + +G_DECLARE_FINAL_TYPE (PhocToplevelAddons, phoc_toplevel_addons, PHOC, TOPLEVEL_ADDONS, GObject) + +PhocToplevelAddons *phoc_toplevel_addons_new (void); +void phoc_toplevel_addons_notify_state (PhocToplevelAddons *self, + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_array *state); + +G_END_DECLS diff --git a/src/view.c b/src/view.c index 21e969878b78ef528bd9b0bccc5cda8b09ba251e..c52801da432113b2a21cb141ab9ffdd233008b22 100644 --- a/src/view.c +++ b/src/view.c @@ -7,6 +7,7 @@ #endif #include #include +#include #include #include @@ -625,9 +626,13 @@ phoc_view_activate (PhocView *self, bool activate) if (!desktop->maximize) phoc_view_appear_activated (self, activate); - if (priv->toplevel_handle) + if (priv->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_activated (priv->toplevel_handle, activate); + if (activate) + PHOC_VIEW_GET_CLASS (self)->ping (self); + } + if (activate && phoc_view_is_fullscreen (self)) phoc_output_force_shell_reveal (priv->fullscreen_output, false); @@ -1834,6 +1839,20 @@ phoc_view_set_close_default (PhocView *self) g_assert_not_reached (); } +G_NORETURN +static void +phoc_view_force_close_default (PhocView *self) +{ + g_assert_not_reached (); +} + +G_NORETURN +static void +phoc_view_ping_default (PhocView *self) +{ + g_assert_not_reached (); +} + static void phoc_view_class_init (PhocViewClass *klass) @@ -1861,6 +1880,8 @@ phoc_view_class_init (PhocViewClass *klass) view_class->set_fullscreen = phoc_view_set_fullscreen_default; view_class->set_maximized = phoc_view_set_maximized_default; view_class->close = phoc_view_set_close_default; + view_class->force_close = phoc_view_force_close_default; + view_class->ping = phoc_view_ping_default; /** * PhocView:scale-to-fit: @@ -2272,6 +2293,25 @@ phoc_view_close (PhocView *self) PHOC_VIEW_GET_CLASS (self)->close (self); } + +void +phoc_view_force_close (PhocView *self) +{ + PhocViewPrivate *priv; + + g_assert (PHOC_IS_VIEW (self)); + priv = phoc_view_get_instance_private (self); + + if (kill (priv->pid, SIGKILL) == 0) + return; + + g_warning ("Failed to forcefully close %s (PID: %d) with SIGKILL: %s", + priv->title, priv->pid, strerror (errno)); + + PHOC_VIEW_GET_CLASS (self)->force_close (self); +} + + struct wlr_surface * phoc_view_get_wlr_surface_at (PhocView *self, double sx, double sy, double *sub_x, double *sub_y) { @@ -2481,3 +2521,29 @@ phoc_view_set_visibility (PhocView *self, gboolean visibility) phoc_view_set_suspended (self, !visibility); } + + +void +phoc_view_notify_unresponsive (PhocView *self) +{ + PhocViewPrivate *priv; + PhocDesktop *desktop; + PhocToplevelAddons *toplevel_addons; + struct wl_array state; + uint32_t *val; + + g_assert (PHOC_IS_VIEW (self)); + priv = phoc_view_get_instance_private (self); + + desktop = phoc_server_get_desktop (phoc_server_get_default ()); + toplevel_addons = phoc_desktop_get_toplevel_addons (desktop); + + wl_array_init (&state); + + val = wl_array_add (&state, sizeof (*val)); + *val = PHOC_TOPLEVEL_ADDONS_STATE_UNRESPONSIVE; + + phoc_toplevel_addons_notify_state (toplevel_addons, priv->toplevel_handle, &state); + + wl_array_release (&state); +} diff --git a/src/view.h b/src/view.h index 22783a2ba7b0df9d4575b7d0f09c5978df1a927a..8cfd3baf97b4a7c6ad6de511a2596dae5d198470 100644 --- a/src/view.h +++ b/src/view.h @@ -105,6 +105,8 @@ struct _PhocView { * @get_wlr_surface_at: Get the wlr_surface at the give coordinates. * The implementation is optional. * @get_alpha: Get the view's alpha value. + * @ping: Check if a view is responsive (i.e. not frozen or stuck). + * @force_close: Force close a view. */ typedef struct _PhocViewClass { @@ -127,6 +129,8 @@ typedef struct _PhocViewClass (*get_wlr_surface_at) (PhocView *self, double sx, double sy, double *sub_x, double *sub_y); pid_t (*get_pid) (PhocView *self); float (*get_alpha) (PhocView *self); + void (*ping) (PhocView *self); + void (*force_close) (PhocView *self); } PhocViewClass; @@ -176,6 +180,7 @@ void phoc_view_set_fullscreen (PhocView *view, bool fullscreen, PhocOutput *output); void phoc_view_close (PhocView *self); +void phoc_view_force_close (PhocView *self); void phoc_view_set_app_id (PhocView *self, const char *app_id); const char *phoc_view_get_app_id (PhocView *self); void phoc_view_for_each_surface (PhocView *self, @@ -217,5 +222,6 @@ gboolean phoc_view_get_tiled_box (PhocView *self, void phoc_view_add_bling (PhocView *self, PhocBling *bling); void phoc_view_remove_bling (PhocView *self, PhocBling *bling); GSList *phoc_view_get_blings (PhocView *self); +void phoc_view_notify_unresponsive (PhocView *self); G_END_DECLS diff --git a/src/xdg-toplevel.c b/src/xdg-toplevel.c index 69d0c82c874a845c8506fba95ddcd21c1c348f83..61d77dd88b9b47c9bc1e437f79b03bb9d886ca1f 100644 --- a/src/xdg-toplevel.c +++ b/src/xdg-toplevel.c @@ -7,7 +7,7 @@ * Author: Guido Günther */ -#define G_LOG_DOMAIN "phoc-xdg-surface" +#define G_LOG_DOMAIN "phoc-xdg-toplevel" #include "phoc-config.h" @@ -53,6 +53,7 @@ typedef struct _PhocXdgToplevel { struct wl_listener new_popup; struct wl_listener map; struct wl_listener unmap; + struct wl_listener ping_timeout; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; @@ -304,6 +305,18 @@ _close (PhocView *view) send_frame_done_if_not_visible (PHOC_XDG_TOPLEVEL (view)); } + +static void +force_close (PhocView *view) +{ + struct wlr_xdg_toplevel *xdg_toplevel = PHOC_XDG_TOPLEVEL (view)->xdg_toplevel; + + wl_resource_post_error (xdg_toplevel->resource, + WL_DISPLAY_ERROR_NO_MEMORY, + "Unresponsive to ping"); +} + + static void for_each_surface (PhocView *view, wlr_surface_iterator_func_t iterator, @@ -379,6 +392,16 @@ phoc_xdg_toplevel_set_capabilities (PhocXdgToplevel *self } +static void +ping (PhocView *view) +{ + PhocXdgToplevel *self = PHOC_XDG_TOPLEVEL (view); + + g_debug ("Pinging %s - %s", self->xdg_toplevel->title, self->xdg_toplevel->app_id); + wlr_xdg_surface_ping (self->xdg_toplevel->base); +} + + static void handle_surface_commit (struct wl_listener *listener, void *data) { @@ -611,6 +634,16 @@ handle_new_popup (struct wl_listener *listener, void *data) } +static void +handle_ping_timeout (struct wl_listener *listener, void *data) +{ + PhocXdgToplevel *self = wl_container_of (listener, self, ping_timeout); + + g_debug ("%s - %s is not responding", self->xdg_toplevel->title, self->xdg_toplevel->app_id); + phoc_view_notify_unresponsive (PHOC_VIEW (self)); +} + + static void phoc_xdg_toplevel_constructed (GObject *object) { @@ -637,6 +670,9 @@ phoc_xdg_toplevel_constructed (GObject *object) self->new_popup.notify = handle_new_popup; wl_signal_add (&self->xdg_toplevel->base->events.new_popup, &self->new_popup); + self->ping_timeout.notify = handle_ping_timeout; + wl_signal_add (&self->xdg_toplevel->base->events.ping_timeout, &self->ping_timeout); + /* Register wlr_xdg_toplevel handlers */ self->request_move.notify = handle_request_move; wl_signal_add (&self->xdg_toplevel->events.request_move, &self->request_move); @@ -680,6 +716,7 @@ phoc_xdg_toplevel_finalize (GObject *object) wl_list_remove (&self->set_title.link); wl_list_remove (&self->set_app_id.link); wl_list_remove (&self->set_parent.link); + wl_list_remove (&self->ping_timeout.link); self->xdg_toplevel->base->data = NULL; G_OBJECT_CLASS (phoc_xdg_toplevel_parent_class)->finalize (object); @@ -706,10 +743,12 @@ phoc_xdg_toplevel_class_init (PhocXdgToplevelClass *klass) view_class->set_tiled = set_tiled; view_class->set_suspended = set_suspended; view_class->close = _close; + view_class->force_close = force_close; view_class->for_each_surface = for_each_surface; view_class->get_geometry = get_geometry; view_class->get_wlr_surface_at = get_wlr_surface_at; view_class->get_pid = get_pid; + view_class->ping = ping; /** * PhocXdgToplevel:wlr-xdg-toplevel: diff --git a/src/xwayland-surface.c b/src/xwayland-surface.c index 244ee8c6a6beb79f2cbf06f74187ea2ce121c2f1..e1929f3bb024259bc8494c68e5580b40268015ff 100644 --- a/src/xwayland-surface.c +++ b/src/xwayland-surface.c @@ -37,6 +37,7 @@ typedef struct _PhocXWaylandSurface { struct wlr_xwayland_surface *xwayland_surface; struct wl_listener destroy; + struct wl_listener ping_timeout; struct wl_listener request_configure; struct wl_listener request_move; struct wl_listener request_resize; @@ -200,6 +201,15 @@ _close (PhocView *view) wlr_xwayland_surface_close (xwayland_surface); } + +static void +force_close (PhocView *view) +{ + // TODO + _close (view); +} + + static bool want_scaling (PhocView *view) { @@ -264,6 +274,16 @@ get_alpha (PhocView *view) } +static void +ping (PhocView *view) +{ + PhocXWaylandSurface *self = PHOC_XWAYLAND_SURFACE (view); + + g_debug ("Pinging %s", self->xwayland_surface->title); + wlr_xwayland_surface_ping (self->xwayland_surface); +} + + static void phoc_xwayland_surface_set_property (GObject *object, guint property_id, @@ -531,6 +551,14 @@ handle_dissociate (struct wl_listener *listener, void *data) } +static void +handle_ping_timeout (struct wl_listener *listener, void *data) +{ + PhocXWaylandSurface *self = wl_container_of (listener, self, ping_timeout); + + g_debug ("%s is not responding", self->xwayland_surface->title); + phoc_view_notify_unresponsive (PHOC_VIEW (self)); +} /* }}} */ @@ -589,6 +617,9 @@ phoc_xwayland_surface_constructed (GObject *object) self->set_opacity.notify = handle_set_opacity; wl_signal_add (&surface->events.set_opacity, &self->set_opacity); + self->ping_timeout.notify = handle_ping_timeout; + wl_signal_add (&self->xwayland_surface->events.ping_timeout, &self->ping_timeout); + wl_list_init (&self->map.link); wl_list_init (&self->unmap.link); } @@ -611,6 +642,7 @@ phoc_xwayland_surface_finalize (GObject *object) wl_list_remove(&self->set_class.link); wl_list_remove(&self->set_startup_id.link); wl_list_remove (&self->set_opacity.link); + wl_list_remove (&self->ping_timeout.link); self->xwayland_surface->data = NULL; @@ -637,8 +669,10 @@ phoc_xwayland_surface_class_init (PhocXWaylandSurfaceClass *klass) view_class->set_fullscreen = set_fullscreen; view_class->set_maximized = set_maximized; view_class->close = _close; + view_class->force_close = force_close; view_class->get_pid = get_pid; view_class->get_alpha = get_alpha; + view_class->ping = ping; /** * PhocXWaylandSurface:wlr-xwayland-surface: