1f1bf4cd9d
For Xwayland, mutter creates the sockets in the standard /tmp/.X11-unix directory. Yet, if that directory already exists, it may have been created by another user with full control over the created socket. To avoid that issue, if the directory /tmp/.X11-unix already exists, check that the permissions are as we expect, i.e. the directory belongs to either root or the user herself, is writable and has the sticky bit. Thanks to fabian@ritter-vogt.de for reporting that issue. https://gitlab.gnome.org/GNOME/mutter/-/issues/1708 Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1787>
1367 lines
39 KiB
C
1367 lines
39 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/*
|
|
* X Wayland Support
|
|
*
|
|
* Copyright (C) 2013 Intel Corporation
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "wayland/meta-xwayland.h"
|
|
#include "wayland/meta-xwayland-private.h"
|
|
|
|
#include <errno.h>
|
|
#include <glib-unix.h>
|
|
#include <glib.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#if defined(HAVE_SYS_RANDOM)
|
|
#include <sys/random.h>
|
|
#elif defined(HAVE_LINUX_RANDOM)
|
|
#include <linux/random.h>
|
|
#endif
|
|
#include <unistd.h>
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <X11/Xauth.h>
|
|
#include <X11/Xlib-xcb.h>
|
|
|
|
#include <xcb/res.h>
|
|
|
|
#include "backends/meta-monitor-manager-private.h"
|
|
#include "backends/meta-settings-private.h"
|
|
#include "core/main-private.h"
|
|
#include "meta/main.h"
|
|
#include "meta/meta-backend.h"
|
|
#include "wayland/meta-xwayland-surface.h"
|
|
#include "x11/meta-x11-display-private.h"
|
|
|
|
#ifdef HAVE_XWAYLAND_LISTENFD
|
|
#define XWAYLAND_LISTENFD "-listenfd"
|
|
#else
|
|
#define XWAYLAND_LISTENFD "-listen"
|
|
#endif
|
|
|
|
#define X11_TMP_UNIX_DIR "/tmp/.X11-unix"
|
|
#define X11_TMP_UNIX_PATH "/tmp/.X11-unix/X"
|
|
|
|
static int display_number_override = -1;
|
|
|
|
static void meta_xwayland_stop_xserver (MetaXWaylandManager *manager);
|
|
|
|
static void
|
|
meta_xwayland_set_primary_output (Display *xdisplay);
|
|
|
|
void
|
|
meta_xwayland_associate_window_with_surface (MetaWindow *window,
|
|
MetaWaylandSurface *surface)
|
|
{
|
|
MetaDisplay *display = window->display;
|
|
MetaXwaylandSurface *xwayland_surface;
|
|
|
|
if (!meta_wayland_surface_assign_role (surface,
|
|
META_TYPE_XWAYLAND_SURFACE,
|
|
NULL))
|
|
{
|
|
wl_resource_post_error (surface->resource,
|
|
WL_DISPLAY_ERROR_INVALID_OBJECT,
|
|
"wl_surface@%d already has a different role",
|
|
wl_resource_get_id (surface->resource));
|
|
return;
|
|
}
|
|
|
|
xwayland_surface = META_XWAYLAND_SURFACE (surface->role);
|
|
meta_xwayland_surface_associate_with_window (xwayland_surface, window);
|
|
|
|
/* Now that we have a surface check if it should have focus. */
|
|
meta_display_sync_wayland_input_focus (display);
|
|
}
|
|
|
|
static gboolean
|
|
associate_window_with_surface_id (MetaXWaylandManager *manager,
|
|
MetaWindow *window,
|
|
guint32 surface_id)
|
|
{
|
|
struct wl_resource *resource;
|
|
|
|
resource = wl_client_get_object (manager->client, surface_id);
|
|
if (resource)
|
|
{
|
|
MetaWaylandSurface *surface = wl_resource_get_user_data (resource);
|
|
meta_xwayland_associate_window_with_surface (window, surface);
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
meta_xwayland_handle_wl_surface_id (MetaWindow *window,
|
|
guint32 surface_id)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
MetaXWaylandManager *manager = &compositor->xwayland_manager;
|
|
|
|
if (!associate_window_with_surface_id (manager, window, surface_id))
|
|
{
|
|
/* No surface ID yet, schedule this association for whenever the
|
|
* surface is made known.
|
|
*/
|
|
meta_wayland_compositor_schedule_surface_association (compositor,
|
|
surface_id, window);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
meta_xwayland_is_xwayland_surface (MetaWaylandSurface *surface)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
MetaXWaylandManager *manager = &compositor->xwayland_manager;
|
|
|
|
return wl_resource_get_client (surface->resource) == manager->client;
|
|
}
|
|
|
|
static char *
|
|
meta_xwayland_get_exe_from_proc_entry (const char *proc_entry)
|
|
{
|
|
g_autofree char *exepath;
|
|
char *executable;
|
|
char *p;
|
|
|
|
exepath = g_file_read_link (proc_entry, NULL);
|
|
if (!exepath)
|
|
return NULL;
|
|
|
|
p = strrchr (exepath, G_DIR_SEPARATOR);
|
|
if (p)
|
|
executable = g_strdup (++p);
|
|
else
|
|
executable = g_strdup (exepath);
|
|
|
|
return executable;
|
|
}
|
|
|
|
static char *
|
|
meta_xwayland_get_exe_from_pid (uint32_t pid)
|
|
{
|
|
g_autofree char *proc_entry;
|
|
char *executable;
|
|
|
|
proc_entry = g_strdup_printf ("/proc/%i/exe", pid);
|
|
executable = meta_xwayland_get_exe_from_proc_entry (proc_entry);
|
|
|
|
return executable;
|
|
}
|
|
|
|
static char *
|
|
meta_xwayland_get_self_exe (void)
|
|
{
|
|
g_autofree char *proc_entry;
|
|
char *executable;
|
|
|
|
proc_entry = g_strdup_printf ("/proc/self/exe");
|
|
executable = meta_xwayland_get_exe_from_proc_entry (proc_entry);
|
|
|
|
return executable;
|
|
}
|
|
|
|
static gboolean
|
|
can_xwayland_ignore_exe (const char *executable,
|
|
const char *self)
|
|
{
|
|
char ** ignore_executables;
|
|
gboolean ret;
|
|
|
|
if (!g_strcmp0 (executable, self))
|
|
return TRUE;
|
|
|
|
ignore_executables = g_strsplit_set (XWAYLAND_IGNORE_EXECUTABLES, ",", -1);
|
|
ret = g_strv_contains ((const char * const *) ignore_executables, executable);
|
|
g_strfreev (ignore_executables);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint32_t
|
|
meta_xwayland_get_client_pid (xcb_connection_t *xcb,
|
|
uint32_t client)
|
|
{
|
|
xcb_res_client_id_spec_t spec = { 0 };
|
|
xcb_res_query_client_ids_cookie_t cookie;
|
|
xcb_res_query_client_ids_reply_t *reply = NULL;
|
|
uint32_t pid = 0, *value;
|
|
|
|
spec.client = client;
|
|
spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
|
|
|
|
cookie = xcb_res_query_client_ids (xcb, 1, &spec);
|
|
reply = xcb_res_query_client_ids_reply (xcb, cookie, NULL);
|
|
|
|
if (reply == NULL)
|
|
return 0;
|
|
|
|
xcb_res_client_id_value_iterator_t it;
|
|
for (it = xcb_res_query_client_ids_ids_iterator (reply);
|
|
it.rem;
|
|
xcb_res_client_id_value_next (&it))
|
|
{
|
|
spec = it.data->spec;
|
|
if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID)
|
|
{
|
|
value = xcb_res_client_id_value_value (it.data);
|
|
pid = *value;
|
|
break;
|
|
}
|
|
}
|
|
free (reply);
|
|
|
|
return pid;
|
|
}
|
|
|
|
static gboolean
|
|
can_terminate_xwayland (Display *xdisplay)
|
|
{
|
|
xcb_connection_t *xcb = XGetXCBConnection (xdisplay);
|
|
xcb_res_query_clients_cookie_t cookie;
|
|
xcb_res_query_clients_reply_t *reply = NULL;
|
|
xcb_res_client_iterator_t it;
|
|
gboolean can_terminate;
|
|
char *self;
|
|
|
|
cookie = xcb_res_query_clients (xcb);
|
|
reply = xcb_res_query_clients_reply (xcb, cookie, NULL);
|
|
|
|
/* Could not get the list of X11 clients, better not terminate Xwayland */
|
|
if (reply == NULL)
|
|
return FALSE;
|
|
|
|
can_terminate = TRUE;
|
|
self = meta_xwayland_get_self_exe ();
|
|
for (it = xcb_res_query_clients_clients_iterator (reply);
|
|
it.rem && can_terminate;
|
|
xcb_res_client_next (&it))
|
|
{
|
|
uint32_t pid;
|
|
char *executable;
|
|
|
|
pid = meta_xwayland_get_client_pid (xcb, it.data->resource_base);
|
|
if (pid == 0)
|
|
{
|
|
/* Unknown PID, don't risk terminating it */
|
|
can_terminate = FALSE;
|
|
break;
|
|
}
|
|
|
|
executable = meta_xwayland_get_exe_from_pid (pid);
|
|
can_terminate = can_xwayland_ignore_exe (executable, self);
|
|
g_free (executable);
|
|
}
|
|
free (reply);
|
|
|
|
return can_terminate;
|
|
}
|
|
|
|
static gboolean
|
|
try_display (int display,
|
|
char **filename_out,
|
|
int *fd_out,
|
|
GError **error)
|
|
{
|
|
gboolean ret = FALSE;
|
|
char *filename;
|
|
int fd;
|
|
|
|
filename = g_strdup_printf ("/tmp/.X%d-lock", display);
|
|
|
|
again:
|
|
fd = open (filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0444);
|
|
|
|
if (fd < 0 && errno == EEXIST)
|
|
{
|
|
char pid[11];
|
|
char *end;
|
|
pid_t other;
|
|
int read_bytes;
|
|
|
|
fd = open (filename, O_CLOEXEC, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to open lock file %s: %s",
|
|
filename, g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
read_bytes = read (fd, pid, 11);
|
|
if (read_bytes != 11)
|
|
{
|
|
if (read_bytes < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to read from lock file %s: %s",
|
|
filename, g_strerror (errno));
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
|
|
"Only read %d bytes (needed 11) from lock file: %s",
|
|
read_bytes, filename);
|
|
}
|
|
goto out;
|
|
}
|
|
close (fd);
|
|
fd = -1;
|
|
|
|
pid[10] = '\0';
|
|
other = strtol (pid, &end, 0);
|
|
if (end != pid + 10)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
|
"Can't parse lock file %s", filename);
|
|
goto out;
|
|
}
|
|
|
|
if (kill (other, 0) < 0 && errno == ESRCH)
|
|
{
|
|
/* Process is dead. Try unlinking the lock file and trying again. */
|
|
if (unlink (filename) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to unlink stale lock file %s: %s",
|
|
filename, g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
goto again;
|
|
}
|
|
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Lock file %s is already occupied", filename);
|
|
goto out;
|
|
}
|
|
else if (fd < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to create lock file %s: %s",
|
|
filename, g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (!ret)
|
|
{
|
|
g_free (filename);
|
|
filename = NULL;
|
|
|
|
if (fd >= 0)
|
|
{
|
|
close (fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
|
|
*filename_out = filename;
|
|
*fd_out = fd;
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
create_lock_file (int display,
|
|
int *display_out,
|
|
GError **error)
|
|
{
|
|
char *filename;
|
|
int fd;
|
|
char pid[12];
|
|
int size;
|
|
int number_of_tries = 0;
|
|
g_autoptr (GError) local_error = NULL;
|
|
|
|
while (!try_display (display, &filename, &fd, &local_error))
|
|
{
|
|
meta_topic (META_DEBUG_WAYLAND,
|
|
"Failed to lock X11 display: %s", local_error->message);
|
|
g_clear_error (&local_error);
|
|
display++;
|
|
number_of_tries++;
|
|
|
|
/* If we can't get a display after 50 times, then something's wrong. Just
|
|
* abort in this case. */
|
|
if (number_of_tries >= 50)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Gave up after trying to lock different "
|
|
"X11 display lock file 50 times");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Subtle detail: we use the pid of the wayland compositor, not the xserver
|
|
* in the lock file. Another subtlety: snprintf returns the number of bytes
|
|
* it _would've_ written without either the NUL or the size clamping, hence
|
|
* the disparity in size. */
|
|
size = snprintf (pid, 12, "%10d\n", getpid ());
|
|
errno = 0;
|
|
if (size != 11 || write (fd, pid, 11) != 11)
|
|
{
|
|
if (errno != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to write pid to lock file %s: %s",
|
|
filename, g_strerror (errno));
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to write pid to lock file %s", filename);
|
|
}
|
|
|
|
unlink (filename);
|
|
close (fd);
|
|
g_free (filename);
|
|
return NULL;
|
|
}
|
|
|
|
close (fd);
|
|
|
|
*display_out = display;
|
|
return filename;
|
|
}
|
|
|
|
static int
|
|
bind_to_abstract_socket (int display,
|
|
GError **error)
|
|
{
|
|
struct sockaddr_un addr;
|
|
socklen_t size, name_size;
|
|
int fd;
|
|
|
|
fd = socket (PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
|
if (fd < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to create socket: %s", g_strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
addr.sun_family = AF_LOCAL;
|
|
name_size = snprintf (addr.sun_path, sizeof addr.sun_path,
|
|
"%c%s%d", 0, X11_TMP_UNIX_PATH, display);
|
|
size = offsetof (struct sockaddr_un, sun_path) + name_size;
|
|
if (bind (fd, (struct sockaddr *) &addr, size) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to bind to %s: %s",
|
|
addr.sun_path + 1, g_strerror (errno));
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
|
|
if (listen (fd, 1) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to listen to %s: %s",
|
|
addr.sun_path + 1, g_strerror (errno));
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
bind_to_unix_socket (int display,
|
|
GError **error)
|
|
{
|
|
struct sockaddr_un addr;
|
|
socklen_t size, name_size;
|
|
int fd;
|
|
|
|
fd = socket (PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
|
if (fd < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to create socket: %s", g_strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
addr.sun_family = AF_LOCAL;
|
|
name_size = snprintf (addr.sun_path, sizeof addr.sun_path,
|
|
"%s%d", X11_TMP_UNIX_PATH, display) + 1;
|
|
size = offsetof (struct sockaddr_un, sun_path) + name_size;
|
|
unlink (addr.sun_path);
|
|
if (bind (fd, (struct sockaddr *) &addr, size) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to bind to %s: %s",
|
|
addr.sun_path, g_strerror (errno));
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
|
|
if (listen (fd, 1) < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to listen to %s: %s",
|
|
addr.sun_path, g_strerror (errno));
|
|
unlink (addr.sun_path);
|
|
close (fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
xserver_died (GObject *source,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GSubprocess *proc = G_SUBPROCESS (source);
|
|
MetaDisplay *display = meta_get_display ();
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!g_subprocess_wait_finish (proc, result, &error))
|
|
{
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
return;
|
|
|
|
g_warning ("Failed to finish waiting for Xwayland: %s", error->message);
|
|
}
|
|
else if (!g_subprocess_get_successful (proc))
|
|
{
|
|
if (meta_get_x11_display_policy () == META_DISPLAY_POLICY_MANDATORY)
|
|
g_warning ("X Wayland crashed; exiting");
|
|
else
|
|
g_warning ("X Wayland crashed; attempting to recover");
|
|
}
|
|
|
|
if (meta_get_x11_display_policy () == META_DISPLAY_POLICY_MANDATORY)
|
|
{
|
|
meta_exit (META_EXIT_ERROR);
|
|
}
|
|
else if (meta_get_x11_display_policy () == META_DISPLAY_POLICY_ON_DEMAND)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (display->x11_display)
|
|
meta_display_shutdown_x11 (display);
|
|
|
|
if (!meta_xwayland_init (&compositor->xwayland_manager,
|
|
compositor->wayland_display,
|
|
&error))
|
|
g_warning ("Failed to init X sockets: %s", error->message);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
shutdown_xwayland_cb (gpointer data)
|
|
{
|
|
MetaXWaylandManager *manager = data;
|
|
MetaDisplay *display = meta_get_display ();
|
|
MetaBackend *backend = meta_get_backend ();
|
|
|
|
if (!meta_settings_is_experimental_feature_enabled (meta_backend_get_settings (backend),
|
|
META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND))
|
|
return G_SOURCE_REMOVE;
|
|
|
|
if (display->x11_display &&
|
|
!can_terminate_xwayland (display->x11_display->xdisplay))
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
meta_verbose ("Shutting down Xwayland");
|
|
manager->xserver_grace_period_id = 0;
|
|
meta_display_shutdown_x11 (display);
|
|
meta_xwayland_stop_xserver (manager);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static int
|
|
x_io_error (Display *display)
|
|
{
|
|
g_warning ("Connection to xwayland lost");
|
|
|
|
if (meta_get_x11_display_policy () == META_DISPLAY_POLICY_MANDATORY)
|
|
meta_exit (META_EXIT_ERROR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_XSETIOERROREXITHANDLER
|
|
static void
|
|
x_io_error_exit (Display *display,
|
|
void *data)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
MetaXWaylandManager *manager = &compositor->xwayland_manager;
|
|
|
|
g_warning ("Xwayland just died, attempting to recover");
|
|
manager->xserver_grace_period_id =
|
|
g_idle_add (shutdown_xwayland_cb, manager);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
meta_xwayland_override_display_number (int number)
|
|
{
|
|
display_number_override = number;
|
|
}
|
|
|
|
static gboolean
|
|
ensure_x11_unix_perms (GError **error)
|
|
{
|
|
struct stat buf;
|
|
|
|
if (lstat (X11_TMP_UNIX_DIR, &buf) != 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to check permissions on directory \"%s\": %s",
|
|
X11_TMP_UNIX_DIR, g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
/* If the directory already exists, it should belong to root or ourselves ... */
|
|
if (buf.st_uid != 0 && buf.st_uid != getuid ())
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
|
"Wrong ownership for directory \"%s\"",
|
|
X11_TMP_UNIX_DIR);
|
|
return FALSE;
|
|
}
|
|
|
|
/* ... be writable ... */
|
|
if ((buf.st_mode & 0022) != 0022)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
|
"Directory \"%s\" is not writable",
|
|
X11_TMP_UNIX_DIR);
|
|
return FALSE;
|
|
}
|
|
|
|
/* ... and have the sticky bit set */
|
|
if ((buf.st_mode & 01000) != 01000)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
|
"Directory \"%s\" is missing the sticky bit",
|
|
X11_TMP_UNIX_DIR);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
ensure_x11_unix_dir (GError **error)
|
|
{
|
|
if (mkdir (X11_TMP_UNIX_DIR, 01777) != 0)
|
|
{
|
|
if (errno == EEXIST)
|
|
return ensure_x11_unix_perms (error);
|
|
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to create directory \"%s\": %s",
|
|
X11_TMP_UNIX_DIR, g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
open_display_sockets (MetaXWaylandManager *manager,
|
|
int display_index,
|
|
int *abstract_fd_out,
|
|
int *unix_fd_out,
|
|
GError **error)
|
|
{
|
|
int abstract_fd, unix_fd;
|
|
|
|
abstract_fd = bind_to_abstract_socket (display_index, error);
|
|
if (abstract_fd < 0)
|
|
return FALSE;
|
|
|
|
unix_fd = bind_to_unix_socket (display_index, error);
|
|
if (unix_fd < 0)
|
|
{
|
|
close (abstract_fd);
|
|
return FALSE;
|
|
}
|
|
|
|
*abstract_fd_out = abstract_fd;
|
|
*unix_fd_out = unix_fd;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
choose_xdisplay (MetaXWaylandManager *manager,
|
|
MetaXWaylandConnection *connection,
|
|
int *display,
|
|
GError **error)
|
|
{
|
|
int number_of_tries = 0;
|
|
char *lock_file = NULL;
|
|
|
|
if (!ensure_x11_unix_dir (error))
|
|
return FALSE;
|
|
|
|
do
|
|
{
|
|
g_autoptr (GError) local_error = NULL;
|
|
|
|
lock_file = create_lock_file (*display, display, &local_error);
|
|
if (!lock_file)
|
|
{
|
|
g_prefix_error (&local_error, "Failed to create an X lock file: ");
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!open_display_sockets (manager, *display,
|
|
&connection->abstract_fd,
|
|
&connection->unix_fd,
|
|
&local_error))
|
|
{
|
|
unlink (lock_file);
|
|
|
|
if (++number_of_tries >= 50)
|
|
{
|
|
g_prefix_error (&local_error, "Failed to bind X11 socket: ");
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
|
|
(*display)++;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
while (1);
|
|
|
|
connection->display_index = *display;
|
|
connection->name = g_strdup_printf (":%d", connection->display_index);
|
|
connection->lock_file = lock_file;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FILE, fclose)
|
|
|
|
static gboolean
|
|
prepare_auth_file (MetaXWaylandManager *manager,
|
|
GError **error)
|
|
{
|
|
Xauth auth_entry = { 0 };
|
|
g_autoptr (FILE) fp = NULL;
|
|
char auth_data[16];
|
|
int fd;
|
|
|
|
manager->auth_file = g_build_filename (g_get_user_runtime_dir (),
|
|
".mutter-Xwaylandauth.XXXXXX",
|
|
NULL);
|
|
|
|
if (getrandom (auth_data, sizeof (auth_data), 0) != sizeof (auth_data))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to get random data: %s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
auth_entry.family = FamilyLocal;
|
|
auth_entry.address = (char *) g_get_host_name ();
|
|
auth_entry.address_length = strlen (auth_entry.address);
|
|
auth_entry.name = (char *) "MIT-MAGIC-COOKIE-1";
|
|
auth_entry.name_length = strlen (auth_entry.name);
|
|
auth_entry.data = auth_data;
|
|
auth_entry.data_length = sizeof (auth_data);
|
|
|
|
fd = g_mkstemp (manager->auth_file);
|
|
if (fd < 0)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to open Xauthority file: %s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
fp = fdopen (fd, "w+");
|
|
if (!fp)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Failed to open Xauthority stream: %s", g_strerror (errno));
|
|
close (fd);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!XauWriteAuth (fp, &auth_entry))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Error writing to Xauthority file: %s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
auth_entry.family = FamilyWild;
|
|
if (!XauWriteAuth (fp, &auth_entry))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Error writing to Xauthority file: %s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (fflush (fp) == EOF)
|
|
{
|
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
|
"Error writing to Xauthority file: %s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
add_local_user_to_xhost (Display *xdisplay)
|
|
{
|
|
XHostAddress host_entry;
|
|
XServerInterpretedAddress siaddr;
|
|
|
|
siaddr.type = (char *) "localuser";
|
|
siaddr.typelength = strlen (siaddr.type);
|
|
siaddr.value = (char *) g_get_user_name();
|
|
siaddr.valuelength = strlen (siaddr.value);
|
|
|
|
host_entry.family = FamilyServerInterpreted;
|
|
host_entry.address = (char *) &siaddr;
|
|
|
|
XAddHost (xdisplay, &host_entry);
|
|
}
|
|
|
|
static void
|
|
on_init_x11_cb (MetaDisplay *display,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!meta_display_init_x11_finish (display, result, &error))
|
|
g_warning ("Failed to initialize X11 display: %s", error->message);
|
|
}
|
|
|
|
static gboolean
|
|
on_displayfd_ready (int fd,
|
|
GIOCondition condition,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task = user_data;
|
|
|
|
/* The server writes its display name to the displayfd
|
|
* socket when it's ready. We don't care about the data
|
|
* in the socket, just that it wrote something, since
|
|
* that means it's ready. */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static int
|
|
steal_fd (int *fd_ptr)
|
|
{
|
|
int fd = *fd_ptr;
|
|
*fd_ptr = -1;
|
|
return fd;
|
|
}
|
|
|
|
void
|
|
meta_xwayland_start_xserver (MetaXWaylandManager *manager,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
struct {
|
|
const char *extension_name;
|
|
MetaXwaylandExtension disable_extension;
|
|
} x11_extension_names[] = {
|
|
{ "SECURITY", META_XWAYLAND_EXTENSION_SECURITY },
|
|
{ "XTEST", META_XWAYLAND_EXTENSION_XTEST },
|
|
};
|
|
|
|
int xwayland_client_fd[2];
|
|
int displayfd[2];
|
|
g_autoptr(GSubprocessLauncher) launcher = NULL;
|
|
GSubprocessFlags flags;
|
|
GError *error = NULL;
|
|
g_autoptr (GTask) task = NULL;
|
|
MetaBackend *backend;
|
|
MetaSettings *settings;
|
|
const char *args[32];
|
|
int xwayland_disable_extensions;
|
|
int i, j;
|
|
|
|
task = g_task_new (NULL, cancellable, callback, user_data);
|
|
g_task_set_source_tag (task, meta_xwayland_start_xserver);
|
|
g_task_set_task_data (task, manager, NULL);
|
|
|
|
/* We want xwayland to be a wayland client so we make a socketpair to setup a
|
|
* wayland protocol connection. */
|
|
if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, xwayland_client_fd) < 0)
|
|
{
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"xwayland_client_fd socketpair failed");
|
|
return;
|
|
}
|
|
|
|
if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, displayfd) < 0)
|
|
{
|
|
close (xwayland_client_fd[0]);
|
|
close (xwayland_client_fd[1]);
|
|
|
|
g_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"displayfd socketpair failed");
|
|
return;
|
|
}
|
|
|
|
/* xwayland, please. */
|
|
flags = G_SUBPROCESS_FLAGS_NONE;
|
|
|
|
if (getenv ("XWAYLAND_STFU"))
|
|
{
|
|
flags |= G_SUBPROCESS_FLAGS_STDOUT_SILENCE;
|
|
flags |= G_SUBPROCESS_FLAGS_STDERR_SILENCE;
|
|
}
|
|
|
|
backend = meta_get_backend ();
|
|
settings = meta_backend_get_settings (backend);
|
|
xwayland_disable_extensions =
|
|
meta_settings_get_xwayland_disable_extensions (settings);
|
|
|
|
launcher = g_subprocess_launcher_new (flags);
|
|
|
|
g_subprocess_launcher_take_fd (launcher,
|
|
steal_fd (&xwayland_client_fd[1]), 3);
|
|
g_subprocess_launcher_take_fd (launcher,
|
|
steal_fd (&manager->public_connection.abstract_fd), 4);
|
|
g_subprocess_launcher_take_fd (launcher,
|
|
steal_fd (&manager->public_connection.unix_fd), 5);
|
|
g_subprocess_launcher_take_fd (launcher,
|
|
steal_fd (&displayfd[1]), 6);
|
|
g_subprocess_launcher_take_fd (launcher,
|
|
steal_fd (&manager->private_connection.abstract_fd), 7);
|
|
|
|
g_subprocess_launcher_setenv (launcher, "WAYLAND_SOCKET", "3", TRUE);
|
|
|
|
i = 0;
|
|
args[i++] = XWAYLAND_PATH;
|
|
args[i++] = manager->public_connection.name;
|
|
args[i++] = "-rootless";
|
|
args[i++] = "-noreset";
|
|
args[i++] = "-accessx";
|
|
args[i++] = "-core";
|
|
args[i++] = "-auth";
|
|
args[i++] = manager->auth_file;
|
|
args[i++] = XWAYLAND_LISTENFD;
|
|
args[i++] = "4";
|
|
args[i++] = XWAYLAND_LISTENFD;
|
|
args[i++] = "5";
|
|
args[i++] = "-displayfd";
|
|
args[i++] = "6";
|
|
#ifdef HAVE_XWAYLAND_INITFD
|
|
args[i++] = "-initfd";
|
|
args[i++] = "7";
|
|
#else
|
|
args[i++] = XWAYLAND_LISTENFD;
|
|
args[i++] = "7";
|
|
#endif
|
|
for (j = 0; j < G_N_ELEMENTS (x11_extension_names); j++)
|
|
{
|
|
/* Make sure we don't go past the array size - We need room for
|
|
* 2 arguments, plus the last NULL terminator.
|
|
*/
|
|
if (i + 3 > G_N_ELEMENTS (args))
|
|
break;
|
|
|
|
if (xwayland_disable_extensions & x11_extension_names[j].disable_extension)
|
|
{
|
|
args[i++] = "-extension";
|
|
args[i++] = x11_extension_names[j].extension_name;
|
|
}
|
|
}
|
|
/* Terminator */
|
|
args[i++] = NULL;
|
|
|
|
manager->proc = g_subprocess_launcher_spawnv (launcher, args, &error);
|
|
|
|
if (!manager->proc)
|
|
{
|
|
close (displayfd[0]);
|
|
close (xwayland_client_fd[0]);
|
|
|
|
g_task_return_error (task, error);
|
|
return;
|
|
}
|
|
|
|
manager->xserver_died_cancellable = g_cancellable_new ();
|
|
g_subprocess_wait_async (manager->proc, manager->xserver_died_cancellable,
|
|
xserver_died, NULL);
|
|
g_unix_fd_add (displayfd[0], G_IO_IN, on_displayfd_ready,
|
|
g_steal_pointer (&task));
|
|
manager->client = wl_client_create (manager->wayland_display,
|
|
xwayland_client_fd[0]);
|
|
}
|
|
|
|
gboolean
|
|
meta_xwayland_start_xserver_finish (MetaXWaylandManager *manager,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_assert (g_task_get_source_tag (G_TASK (result)) ==
|
|
meta_xwayland_start_xserver);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
static gboolean
|
|
xdisplay_connection_activity_cb (gint fd,
|
|
GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
MetaXWaylandManager *manager = user_data;
|
|
MetaDisplay *display = meta_get_display ();
|
|
|
|
meta_display_init_x11 (display, NULL,
|
|
(GAsyncReadyCallback) on_init_x11_cb, NULL);
|
|
|
|
/* Stop watching both file descriptors */
|
|
g_clear_handle_id (&manager->abstract_fd_watch_id, g_source_remove);
|
|
g_clear_handle_id (&manager->unix_fd_watch_id, g_source_remove);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
meta_xwayland_stop_xserver_timeout (MetaXWaylandManager *manager)
|
|
{
|
|
if (manager->xserver_grace_period_id)
|
|
return;
|
|
|
|
manager->xserver_grace_period_id =
|
|
g_timeout_add_seconds (10, shutdown_xwayland_cb, manager);
|
|
}
|
|
|
|
static void
|
|
window_unmanaged_cb (MetaWindow *window,
|
|
MetaXWaylandManager *manager)
|
|
{
|
|
manager->x11_windows = g_list_remove (manager->x11_windows, window);
|
|
g_signal_handlers_disconnect_by_func (window,
|
|
window_unmanaged_cb,
|
|
manager);
|
|
if (!manager->x11_windows)
|
|
{
|
|
meta_verbose ("All X11 windows gone, setting shutdown timeout");
|
|
meta_xwayland_stop_xserver_timeout (manager);
|
|
}
|
|
}
|
|
|
|
static void
|
|
window_created_cb (MetaDisplay *display,
|
|
MetaWindow *window,
|
|
MetaXWaylandManager *manager)
|
|
{
|
|
/* Ignore all internal windows */
|
|
if (!window->xwindow ||
|
|
meta_window_get_pid (window) == getpid ())
|
|
return;
|
|
|
|
manager->x11_windows = g_list_prepend (manager->x11_windows, window);
|
|
g_signal_connect (window, "unmanaged",
|
|
G_CALLBACK (window_unmanaged_cb), manager);
|
|
|
|
g_clear_handle_id (&manager->xserver_grace_period_id, g_source_remove);
|
|
}
|
|
|
|
static void
|
|
meta_xwayland_stop_xserver (MetaXWaylandManager *manager)
|
|
{
|
|
if (manager->proc)
|
|
g_subprocess_send_signal (manager->proc, SIGTERM);
|
|
g_signal_handlers_disconnect_by_func (meta_get_display (),
|
|
window_created_cb,
|
|
manager);
|
|
g_clear_object (&manager->xserver_died_cancellable);
|
|
g_clear_object (&manager->proc);
|
|
}
|
|
|
|
gboolean
|
|
meta_xwayland_init (MetaXWaylandManager *manager,
|
|
struct wl_display *wl_display,
|
|
GError **error)
|
|
{
|
|
MetaDisplayPolicy policy;
|
|
int display = 0;
|
|
|
|
if (display_number_override != -1)
|
|
display = display_number_override;
|
|
else if (g_getenv ("RUNNING_UNDER_GDM"))
|
|
display = 1024;
|
|
|
|
|
|
if (!manager->public_connection.name)
|
|
{
|
|
if (!choose_xdisplay (manager, &manager->public_connection, &display, error))
|
|
return FALSE;
|
|
|
|
display++;
|
|
if (!choose_xdisplay (manager, &manager->private_connection, &display, error))
|
|
return FALSE;
|
|
|
|
if (!prepare_auth_file (manager, error))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!open_display_sockets (manager,
|
|
manager->public_connection.display_index,
|
|
&manager->public_connection.abstract_fd,
|
|
&manager->public_connection.unix_fd,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (!open_display_sockets (manager,
|
|
manager->private_connection.display_index,
|
|
&manager->private_connection.abstract_fd,
|
|
&manager->private_connection.unix_fd,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
g_message ("Using public X11 display %s, (using %s for managed services)",
|
|
manager->public_connection.name,
|
|
manager->private_connection.name);
|
|
|
|
manager->wayland_display = wl_display;
|
|
policy = meta_get_x11_display_policy ();
|
|
|
|
if (policy == META_DISPLAY_POLICY_ON_DEMAND)
|
|
{
|
|
manager->abstract_fd_watch_id =
|
|
g_unix_fd_add (manager->public_connection.abstract_fd, G_IO_IN,
|
|
xdisplay_connection_activity_cb, manager);
|
|
manager->unix_fd_watch_id =
|
|
g_unix_fd_add (manager->public_connection.unix_fd, G_IO_IN,
|
|
xdisplay_connection_activity_cb, manager);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
monitors_changed_cb (MetaMonitorManager *monitor_manager)
|
|
{
|
|
MetaX11Display *x11_display = meta_get_display ()->x11_display;
|
|
|
|
meta_xwayland_set_primary_output (x11_display->xdisplay);
|
|
}
|
|
|
|
static void
|
|
on_x11_display_closing (MetaDisplay *display)
|
|
{
|
|
Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display);
|
|
|
|
meta_xwayland_shutdown_dnd (xdisplay);
|
|
g_signal_handlers_disconnect_by_func (meta_monitor_manager_get (),
|
|
monitors_changed_cb,
|
|
NULL);
|
|
g_signal_handlers_disconnect_by_func (display,
|
|
on_x11_display_closing,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
meta_xwayland_init_xrandr (MetaXWaylandManager *manager,
|
|
Display *xdisplay)
|
|
{
|
|
MetaMonitorManager *monitor_manager = meta_monitor_manager_get ();
|
|
|
|
manager->has_xrandr = XRRQueryExtension (xdisplay,
|
|
&manager->rr_event_base,
|
|
&manager->rr_error_base);
|
|
|
|
if (!manager->has_xrandr)
|
|
return;
|
|
|
|
XRRSelectInput (xdisplay, DefaultRootWindow (xdisplay),
|
|
RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);
|
|
|
|
g_signal_connect (monitor_manager, "monitors-changed",
|
|
G_CALLBACK (monitors_changed_cb), NULL);
|
|
|
|
meta_xwayland_set_primary_output (xdisplay);
|
|
}
|
|
|
|
/* To be called right after connecting */
|
|
void
|
|
meta_xwayland_complete_init (MetaDisplay *display,
|
|
Display *xdisplay)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
MetaXWaylandManager *manager = &compositor->xwayland_manager;
|
|
|
|
/* We install an X IO error handler in addition to the child watch,
|
|
because after Xlib connects our child watch may not be called soon
|
|
enough, and therefore we won't crash when X exits (and most important
|
|
we won't reset the tty).
|
|
*/
|
|
XSetIOErrorHandler (x_io_error);
|
|
#ifdef HAVE_XSETIOERROREXITHANDLER
|
|
XSetIOErrorExitHandler (xdisplay, x_io_error_exit, display);
|
|
#endif
|
|
|
|
g_signal_connect (display, "x11-display-closing",
|
|
G_CALLBACK (on_x11_display_closing), NULL);
|
|
meta_xwayland_init_dnd (xdisplay);
|
|
add_local_user_to_xhost (xdisplay);
|
|
meta_xwayland_init_xrandr (manager, xdisplay);
|
|
|
|
if (meta_get_x11_display_policy () == META_DISPLAY_POLICY_ON_DEMAND)
|
|
{
|
|
meta_xwayland_stop_xserver_timeout (manager);
|
|
g_signal_connect (meta_get_display (), "window-created",
|
|
G_CALLBACK (window_created_cb), manager);
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_xwayland_connection_release (MetaXWaylandConnection *connection)
|
|
{
|
|
unlink (connection->lock_file);
|
|
g_clear_pointer (&connection->lock_file, g_free);
|
|
}
|
|
|
|
void
|
|
meta_xwayland_shutdown (MetaXWaylandManager *manager)
|
|
{
|
|
char path[256];
|
|
|
|
g_cancellable_cancel (manager->xserver_died_cancellable);
|
|
|
|
snprintf (path, sizeof path, "%s%d", X11_TMP_UNIX_PATH,
|
|
manager->public_connection.display_index);
|
|
unlink (path);
|
|
|
|
snprintf (path, sizeof path, "%s%d", X11_TMP_UNIX_PATH,
|
|
manager->private_connection.display_index);
|
|
unlink (path);
|
|
|
|
g_clear_pointer (&manager->public_connection.name, g_free);
|
|
g_clear_pointer (&manager->private_connection.name, g_free);
|
|
|
|
meta_xwayland_connection_release (&manager->public_connection);
|
|
meta_xwayland_connection_release (&manager->private_connection);
|
|
|
|
if (manager->auth_file)
|
|
{
|
|
unlink (manager->auth_file);
|
|
g_clear_pointer (&manager->auth_file, g_free);
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_xwayland_set_primary_output (Display *xdisplay)
|
|
{
|
|
XRRScreenResources *resources;
|
|
MetaMonitorManager *monitor_manager;
|
|
MetaLogicalMonitor *primary_monitor;
|
|
int i;
|
|
|
|
monitor_manager = meta_monitor_manager_get ();
|
|
primary_monitor =
|
|
meta_monitor_manager_get_primary_logical_monitor (monitor_manager);
|
|
|
|
if (!primary_monitor)
|
|
return;
|
|
|
|
resources = XRRGetScreenResourcesCurrent (xdisplay,
|
|
DefaultRootWindow (xdisplay));
|
|
if (!resources)
|
|
return;
|
|
|
|
for (i = 0; i < resources->noutput; i++)
|
|
{
|
|
RROutput output_id = resources->outputs[i];
|
|
XRROutputInfo *xrandr_output;
|
|
XRRCrtcInfo *crtc_info = NULL;
|
|
MetaRectangle crtc_geometry;
|
|
|
|
xrandr_output = XRRGetOutputInfo (xdisplay, resources, output_id);
|
|
if (!xrandr_output)
|
|
continue;
|
|
|
|
if (xrandr_output->crtc)
|
|
crtc_info = XRRGetCrtcInfo (xdisplay, resources, xrandr_output->crtc);
|
|
|
|
XRRFreeOutputInfo (xrandr_output);
|
|
|
|
if (!crtc_info)
|
|
continue;
|
|
|
|
crtc_geometry.x = crtc_info->x;
|
|
crtc_geometry.y = crtc_info->y;
|
|
crtc_geometry.width = crtc_info->width;
|
|
crtc_geometry.height = crtc_info->height;
|
|
|
|
XRRFreeCrtcInfo (crtc_info);
|
|
|
|
if (meta_rectangle_equal (&crtc_geometry, &primary_monitor->rect))
|
|
{
|
|
XRRSetOutputPrimary (xdisplay, DefaultRootWindow (xdisplay),
|
|
output_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
XRRFreeScreenResources (resources);
|
|
}
|
|
|
|
gboolean
|
|
meta_xwayland_handle_xevent (XEvent *event)
|
|
{
|
|
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
|
|
MetaXWaylandManager *manager = &compositor->xwayland_manager;
|
|
|
|
if (meta_xwayland_dnd_handle_event (event))
|
|
return TRUE;
|
|
|
|
if (manager->has_xrandr && event->type == manager->rr_event_base + RRNotify)
|
|
{
|
|
MetaX11Display *x11_display = meta_get_display ()->x11_display;
|
|
meta_xwayland_set_primary_output (x11_display->xdisplay);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|