13d9edc687
There are existing extensions that implement desktop icons as a combination of a GTK program and a small extension to make the wayland window behave as if it was of type DESKTOP on X11. That's quite painful, as it requires reimplementing WM behavior that is already implemented in mutter itself (stacking, stickiness, skip-taskbar, ...), as well as modifying gnome-shell to consider the window in addition to "real" DESKTOP windows (workspace-switch animations, ctrl-alt-tab, ...). In addition to that, other extensions may also have special handling of DESKTOP windows, and their code cannot easily be monkey-patched to handle "alternative" desktop icons. This whole game of whack-a-mole can easily be avoided by allowing desktop-icons extensions to mark their desktop windows as DESKTOP, so do just that. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3305>
522 lines
15 KiB
C
522 lines
15 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/*
|
|
* Copyright 2019 Sergio Costas (rastersoft@gmail.com)
|
|
* Copyright 2023 Red Hat
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* SECTION: meta-wayland-client
|
|
* @title MetaWaylandClient
|
|
* @include: gio/gsubprocess.h
|
|
* A class that allows to launch a trusted client and detect if an specific
|
|
* Wayland window belongs to it.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "wayland/meta-wayland-client-private.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib-object.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <wayland-server.h>
|
|
|
|
#include "core/window-private.h"
|
|
#include "meta/util.h"
|
|
#include "wayland/meta-wayland-private.h"
|
|
#include "wayland/meta-wayland-types.h"
|
|
#include "wayland/meta-window-wayland.h"
|
|
|
|
enum
|
|
{
|
|
CLIENT_DESTROYED,
|
|
|
|
N_SIGNALS
|
|
};
|
|
|
|
static guint signals[N_SIGNALS];
|
|
|
|
struct _MetaWaylandClient
|
|
{
|
|
GObject parent_instance;
|
|
|
|
MetaContext *context;
|
|
|
|
struct {
|
|
GSubprocessLauncher *launcher;
|
|
GSubprocess *subprocess;
|
|
GCancellable *died_cancellable;
|
|
gboolean process_running;
|
|
gboolean process_launched;
|
|
} subprocess;
|
|
|
|
struct {
|
|
int fd;
|
|
} indirect;
|
|
|
|
struct wl_client *wayland_client;
|
|
struct wl_listener client_destroy_listener;
|
|
MetaServiceClientType service_client_type;
|
|
};
|
|
|
|
G_DEFINE_TYPE (MetaWaylandClient, meta_wayland_client, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
meta_wayland_client_dispose (GObject *object)
|
|
{
|
|
MetaWaylandClient *client = META_WAYLAND_CLIENT (object);
|
|
|
|
g_clear_pointer (&client->wayland_client, wl_client_destroy);
|
|
g_cancellable_cancel (client->subprocess.died_cancellable);
|
|
g_clear_object (&client->subprocess.died_cancellable);
|
|
g_clear_object (&client->subprocess.launcher);
|
|
g_clear_object (&client->subprocess.subprocess);
|
|
|
|
G_OBJECT_CLASS (meta_wayland_client_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
meta_wayland_client_class_init (MetaWaylandClientClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = meta_wayland_client_dispose;
|
|
|
|
signals[CLIENT_DESTROYED] = g_signal_new ("client-destroyed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
meta_wayland_client_init (MetaWaylandClient *client)
|
|
{
|
|
client->service_client_type = META_SERVICE_CLIENT_TYPE_NONE;
|
|
}
|
|
|
|
static void
|
|
process_died (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
MetaWaylandClient *client = META_WAYLAND_CLIENT (user_data);
|
|
|
|
client->subprocess.process_running = FALSE;
|
|
}
|
|
|
|
static void
|
|
child_setup (gpointer user_data)
|
|
{
|
|
MetaDisplay *display = user_data;
|
|
MetaContext *context = meta_display_get_context (display);
|
|
|
|
meta_context_restore_rlimit_nofile (context, NULL);
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_new_indirect: (skip)
|
|
*/
|
|
MetaWaylandClient *
|
|
meta_wayland_client_new_indirect (MetaContext *context,
|
|
GError **error)
|
|
{
|
|
MetaWaylandClient *client;
|
|
|
|
if (!meta_is_wayland_compositor ())
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"MetaWaylandClient can be used only with Wayland.");
|
|
return NULL;
|
|
}
|
|
|
|
client = g_object_new (META_TYPE_WAYLAND_CLIENT, NULL);
|
|
client->context = context;
|
|
|
|
return client;
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_new:
|
|
* @context: (not nullable): a #MetaContext
|
|
* @launcher: (not nullable): a GSubprocessLauncher to use to launch the subprocess
|
|
* @error: (nullable): Error
|
|
*
|
|
* Creates a new #MetaWaylandClient. The GSubprocesslauncher passed is
|
|
* stored internally and will be used to launch the subprocess.
|
|
*
|
|
* Returns: A #MetaWaylandClient or %NULL if %error is set. Free with
|
|
* g_object_unref().
|
|
*/
|
|
MetaWaylandClient *
|
|
meta_wayland_client_new (MetaContext *context,
|
|
GSubprocessLauncher *launcher,
|
|
GError **error)
|
|
{
|
|
MetaWaylandClient *client;
|
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
if (!meta_is_wayland_compositor ())
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"MetaWaylandClient can be used only with Wayland.");
|
|
return NULL;
|
|
}
|
|
|
|
if (launcher == NULL)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"Invalid launcher.");
|
|
return NULL;
|
|
}
|
|
|
|
client = g_object_new (META_TYPE_WAYLAND_CLIENT, NULL);
|
|
client->context = context;
|
|
client->subprocess.launcher = g_object_ref (launcher);
|
|
return client;
|
|
}
|
|
|
|
static gboolean
|
|
init_wayland_client (MetaWaylandClient *client,
|
|
struct wl_client **wayland_client,
|
|
int *fd,
|
|
GError **error)
|
|
{
|
|
MetaWaylandCompositor *compositor;
|
|
int client_fd[2];
|
|
|
|
if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, client_fd) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create a socket pair for the wayland client.");
|
|
return FALSE;
|
|
}
|
|
|
|
compositor = meta_context_get_wayland_compositor (client->context);
|
|
|
|
*wayland_client = wl_client_create (compositor->wayland_display, client_fd[0]);
|
|
*fd = client_fd[1];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
client_destroyed_cb (struct wl_listener *listener,
|
|
void *user_data)
|
|
{
|
|
MetaWaylandClient *client = wl_container_of (listener, client,
|
|
client_destroy_listener);
|
|
|
|
client->wayland_client = NULL;
|
|
g_signal_emit (client, signals[CLIENT_DESTROYED], 0);
|
|
}
|
|
|
|
static void
|
|
set_wayland_client (MetaWaylandClient *client,
|
|
struct wl_client *wayland_client)
|
|
{
|
|
client->wayland_client = wayland_client;
|
|
|
|
client->client_destroy_listener.notify = client_destroyed_cb;
|
|
wl_client_add_destroy_listener (wayland_client,
|
|
&client->client_destroy_listener);
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_setup_fd: (skip)
|
|
* @client: a #MetaWaylandClient
|
|
*
|
|
* Initialize a wl_client that can be connected to via the returned file
|
|
* descriptor. May only be used with a #MetaWaylandClient created with
|
|
* meta_wayland_client_new_indirect().
|
|
*
|
|
* Returns: (transfer full): A new file descriptor
|
|
*/
|
|
int
|
|
meta_wayland_client_setup_fd (MetaWaylandClient *client,
|
|
GError **error)
|
|
{
|
|
struct wl_client *wayland_client;
|
|
int fd;
|
|
|
|
g_return_val_if_fail (!client->wayland_client, -1);
|
|
g_return_val_if_fail (!client->subprocess.launcher, -1);
|
|
|
|
if (!init_wayland_client (client, &wayland_client, &fd, error))
|
|
return -1;
|
|
|
|
set_wayland_client (client, wayland_client);
|
|
|
|
return fd;
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_spawnv:
|
|
* @client: a #MetaWaylandClient
|
|
* @display: (not nullable): the current MetaDisplay
|
|
* @argv: (array zero-terminated=1) (element-type filename): Command line arguments
|
|
* @error: (nullable): Error
|
|
*
|
|
* Creates a #GSubprocess given a provided array of arguments, launching a new
|
|
* process with the binary specified in the first element of argv, and with the
|
|
* rest of elements as parameters. It also sets up a new Wayland socket and sets
|
|
* the environment variable WAYLAND_SOCKET to make the new process to use it.
|
|
*
|
|
* Returns: (transfer full): A new #GSubprocess, or %NULL on error (and @error
|
|
* will be set)
|
|
**/
|
|
GSubprocess *
|
|
meta_wayland_client_spawnv (MetaWaylandClient *client,
|
|
MetaDisplay *display,
|
|
const char * const *argv,
|
|
GError **error)
|
|
{
|
|
GSubprocess *subprocess;
|
|
struct wl_client *wayland_client;
|
|
int fd;
|
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
g_return_val_if_fail (argv != NULL &&
|
|
argv[0] != NULL &&
|
|
argv[0][0] != '\0',
|
|
NULL);
|
|
|
|
if (!client->subprocess.launcher)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"This client can not be launched");
|
|
return NULL;
|
|
}
|
|
|
|
if (client->subprocess.process_launched)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"This object already has spawned a subprocess.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!client->subprocess.launcher)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_INITIALIZED,
|
|
"MetaWaylandClient must be created using meta_wayland_client_new().");
|
|
return NULL;
|
|
}
|
|
|
|
if (!init_wayland_client (client, &wayland_client, &fd, error))
|
|
return NULL;
|
|
|
|
g_subprocess_launcher_take_fd (client->subprocess.launcher, fd, 3);
|
|
g_subprocess_launcher_setenv (client->subprocess.launcher,
|
|
"WAYLAND_SOCKET", "3", TRUE);
|
|
g_subprocess_launcher_set_child_setup (client->subprocess.launcher,
|
|
child_setup, display, NULL);
|
|
subprocess = g_subprocess_launcher_spawnv (client->subprocess.launcher, argv,
|
|
error);
|
|
g_clear_object (&client->subprocess.launcher);
|
|
client->subprocess.process_launched = TRUE;
|
|
|
|
if (subprocess == NULL)
|
|
return NULL;
|
|
|
|
set_wayland_client (client, wayland_client);
|
|
|
|
client->subprocess.subprocess = subprocess;
|
|
client->subprocess.process_running = TRUE;
|
|
client->subprocess.died_cancellable = g_cancellable_new ();
|
|
g_subprocess_wait_async (client->subprocess.subprocess,
|
|
client->subprocess.died_cancellable,
|
|
process_died,
|
|
client);
|
|
|
|
return g_object_ref (client->subprocess.subprocess);
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_spawn:
|
|
* @client: a #MetaWaylandClient
|
|
* @display: (not nullable): the current MetaDisplay
|
|
* @error: (nullable): Error
|
|
* @argv0: Command line arguments
|
|
* @...: Continued arguments, %NULL terminated
|
|
*
|
|
* Creates a #GSubprocess given a provided varargs list of arguments. It also
|
|
* sets up a new Wayland socket and sets the environment variable WAYLAND_SOCKET
|
|
* to make the new process to use it.
|
|
*
|
|
* Returns: (transfer full): A new #GSubprocess, or %NULL on error (and @error
|
|
* will be set)
|
|
**/
|
|
GSubprocess *
|
|
meta_wayland_client_spawn (MetaWaylandClient *client,
|
|
MetaDisplay *display,
|
|
GError **error,
|
|
const char *argv0,
|
|
...)
|
|
{
|
|
g_autoptr (GPtrArray) args = NULL;
|
|
GSubprocess *result;
|
|
const char *arg;
|
|
va_list ap;
|
|
|
|
g_return_val_if_fail (argv0 != NULL && argv0[0] != '\0', NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
args = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
va_start (ap, argv0);
|
|
g_ptr_array_add (args, (char *) argv0);
|
|
while ((arg = va_arg (ap, const char *)))
|
|
g_ptr_array_add (args, (char *) arg);
|
|
|
|
g_ptr_array_add (args, NULL);
|
|
va_end (ap);
|
|
|
|
result = meta_wayland_client_spawnv (client,
|
|
display,
|
|
(const char * const *) args->pdata,
|
|
error);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_owns_wayland_window
|
|
* @client: a #MetaWaylandClient
|
|
* @window: (not nullable): a MetaWindow
|
|
*
|
|
* Checks whether @window belongs to the process launched from @client or not.
|
|
* This only works under Wayland. If the window is an X11 window, an exception
|
|
* will be triggered.
|
|
*
|
|
* Returns: TRUE if the window was created by this process; FALSE if not.
|
|
*/
|
|
gboolean
|
|
meta_wayland_client_owns_window (MetaWaylandClient *client,
|
|
MetaWindow *window)
|
|
{
|
|
MetaWaylandSurface *surface;
|
|
|
|
g_return_val_if_fail (meta_is_wayland_compositor (), FALSE);
|
|
g_return_val_if_fail (client->subprocess.subprocess != NULL, FALSE);
|
|
g_return_val_if_fail (client->subprocess.process_running, FALSE);
|
|
|
|
surface = meta_window_get_wayland_surface (window);
|
|
if (surface == NULL || surface->resource == NULL)
|
|
return FALSE;
|
|
|
|
return wl_resource_get_client (surface->resource) == client->wayland_client;
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_skip_from_window_list
|
|
* @client: a #MetaWaylandClient
|
|
* @window: (not nullable): a MetaWindow
|
|
*
|
|
* Hides this window from any window list, like taskbars, pagers...
|
|
*/
|
|
void
|
|
meta_wayland_client_hide_from_window_list (MetaWaylandClient *client,
|
|
MetaWindow *window)
|
|
{
|
|
if (!meta_wayland_client_owns_window (client, window))
|
|
return;
|
|
|
|
if (!window->skip_from_window_list)
|
|
{
|
|
window->skip_from_window_list = TRUE;
|
|
meta_window_recalc_features (window);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_show_in_window_list
|
|
* @client: a #MetaWaylandClient
|
|
* @window: (not nullable): a MetaWindow
|
|
*
|
|
* Shows again this window in window lists, like taskbars, pagers...
|
|
*/
|
|
void
|
|
meta_wayland_client_show_in_window_list (MetaWaylandClient *client,
|
|
MetaWindow *window)
|
|
{
|
|
if (!meta_wayland_client_owns_window (client, window))
|
|
return;
|
|
|
|
if (window->skip_from_window_list)
|
|
{
|
|
window->skip_from_window_list = FALSE;
|
|
meta_window_recalc_features (window);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* meta_wayland_client_make_desktop
|
|
* @client: a #MetaWaylandClient
|
|
* @window: (not nullable): a MetaWindow
|
|
*
|
|
* Mark window as DESKTOP window
|
|
*/
|
|
void
|
|
meta_wayland_client_make_desktop (MetaWaylandClient *client,
|
|
MetaWindow *window)
|
|
{
|
|
g_return_if_fail (META_IS_WAYLAND_CLIENT (client));
|
|
g_return_if_fail (META_IS_WINDOW (window));
|
|
g_return_if_fail (window->type == META_WINDOW_NORMAL);
|
|
|
|
if (!meta_wayland_client_owns_window (client, window))
|
|
return;
|
|
|
|
meta_window_set_type (window, META_WINDOW_DESKTOP);
|
|
}
|
|
|
|
gboolean
|
|
meta_wayland_client_matches (MetaWaylandClient *client,
|
|
const struct wl_client *wayland_client)
|
|
{
|
|
g_return_val_if_fail (wayland_client, FALSE);
|
|
g_return_val_if_fail (client->wayland_client, FALSE);
|
|
|
|
return client->wayland_client == wayland_client;
|
|
}
|
|
|
|
void
|
|
meta_wayland_client_assign_service_client_type (MetaWaylandClient *client,
|
|
MetaServiceClientType service_client_type)
|
|
{
|
|
g_return_if_fail (client->service_client_type ==
|
|
META_SERVICE_CLIENT_TYPE_NONE);
|
|
client->service_client_type = service_client_type;
|
|
}
|
|
|
|
MetaServiceClientType
|
|
meta_wayland_client_get_service_client_type (MetaWaylandClient *client)
|
|
{
|
|
return client->service_client_type;
|
|
}
|