/* * Copyright © 2010 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ #include "config.h" #define VK_USE_PLATFORM_WAYLAND_KHR #include #include #include #include #include #ifdef HAVE_LINUX_MEMFD_H #include #endif #include #include #include #include #include "gdkwayland.h" #include "gdkdisplay.h" #include "gdkdisplay-wayland.h" #include "gdkmonitor-wayland.h" #include "gdkinternals.h" #include "gdkdeviceprivate.h" #include "gdkkeysprivate.h" #include "gdkprivate-wayland.h" #include "gdkglcontext-wayland.h" #include "gdkvulkancontext-wayland.h" #include "gdkwaylandmonitor.h" #include #include "tablet-unstable-v2-client-protocol.h" #include #include #include #include "wm-button-layout-translation.h" /** * SECTION:wayland_interaction * @Short_description: Wayland backend-specific functions * @Title: Wayland Interaction * * The functions in this section are specific to the GDK Wayland backend. * To use them, you need to include the `` header and use * the Wayland-specific pkg-config files to build your application (either * `gdk-wayland-3.0` or `gtk+-wayland-3.0`). * * To make your code compile with other GDK backends, guard backend-specific * calls by an ifdef as follows. Since GDK may be built with multiple * backends, you should also check for the backend that is in use (e.g. by * using the GDK_IS_WAYLAND_DISPLAY() macro). * |[ * #ifdef GDK_WINDOWING_WAYLAND * if (GDK_IS_WAYLAND_DISPLAY (display)) * { * // make Wayland-specific calls here * } * else * #endif * #ifdef GDK_WINDOWING_X11 * if (GDK_IS_X11_DISPLAY (display)) * { * // make X11-specific calls here * } * else * #endif * g_error ("Unsupported GDK backend"); * ]| */ #define MIN_SYSTEM_BELL_DELAY_MS 20 #define GTK_SHELL1_VERSION 2 #define OUTPUT_VERSION_WITH_DONE 2 static void _gdk_wayland_display_load_cursor_theme (GdkWaylandDisplay *display_wayland); G_DEFINE_TYPE (GdkWaylandDisplay, gdk_wayland_display, GDK_TYPE_DISPLAY) static void async_roundtrip_callback (void *data, struct wl_callback *callback, uint32_t time) { GdkWaylandDisplay *display_wayland = data; display_wayland->async_roundtrips = g_list_remove (display_wayland->async_roundtrips, callback); wl_callback_destroy (callback); } static const struct wl_callback_listener async_roundtrip_listener = { async_roundtrip_callback }; static void _gdk_wayland_display_async_roundtrip (GdkWaylandDisplay *display_wayland) { struct wl_callback *callback; callback = wl_display_sync (display_wayland->wl_display); wl_callback_add_listener (callback, &async_roundtrip_listener, display_wayland); display_wayland->async_roundtrips = g_list_append (display_wayland->async_roundtrips, callback); } static void xdg_shell_ping (void *data, struct zxdg_shell_v6 *xdg_shell, uint32_t serial) { GdkWaylandDisplay *display_wayland = data; _gdk_wayland_display_update_serial (display_wayland, serial); GDK_DISPLAY_NOTE (GDK_DISPLAY (data), EVENTS, g_message ("ping, shell %p, serial %u\n", xdg_shell, serial)); zxdg_shell_v6_pong (xdg_shell, serial); } static const struct zxdg_shell_v6_listener xdg_shell_listener = { xdg_shell_ping, }; static gboolean is_known_global (gpointer key, gpointer value, gpointer user_data) { const char *required_global = user_data; const char *known_global = value; return g_strcmp0 (required_global, known_global) == 0; } static gboolean has_required_globals (GdkWaylandDisplay *display_wayland, const char *required_globals[]) { int i = 0; while (required_globals[i]) { if (g_hash_table_find (display_wayland->known_globals, is_known_global, (gpointer)required_globals[i]) == NULL) return FALSE; i++; } return TRUE; } typedef struct _OnHasGlobalsClosure OnHasGlobalsClosure; typedef void (*HasGlobalsCallback) (GdkWaylandDisplay *display_wayland, OnHasGlobalsClosure *closure); struct _OnHasGlobalsClosure { HasGlobalsCallback handler; const char **required_globals; }; static void process_on_globals_closures (GdkWaylandDisplay *display_wayland) { GList *iter; iter = display_wayland->on_has_globals_closures; while (iter != NULL) { GList *next = iter->next; OnHasGlobalsClosure *closure = iter->data; if (has_required_globals (display_wayland, closure->required_globals)) { closure->handler (display_wayland, closure); g_free (closure); display_wayland->on_has_globals_closures = g_list_delete_link (display_wayland->on_has_globals_closures, iter); } iter = next; } } typedef struct { OnHasGlobalsClosure base; uint32_t id; uint32_t version; } SeatAddedClosure; static void _gdk_wayland_display_add_seat (GdkWaylandDisplay *display_wayland, uint32_t id, uint32_t version) { struct wl_seat *seat; display_wayland->seat_version = MIN (version, 5); seat = wl_registry_bind (display_wayland->wl_registry, id, &wl_seat_interface, display_wayland->seat_version); _gdk_wayland_display_create_seat (display_wayland, id, seat); _gdk_wayland_display_async_roundtrip (display_wayland); } static void seat_added_closure_run (GdkWaylandDisplay *display_wayland, OnHasGlobalsClosure *closure) { SeatAddedClosure *seat_added_closure = (SeatAddedClosure*)closure; _gdk_wayland_display_add_seat (display_wayland, seat_added_closure->id, seat_added_closure->version); } static void postpone_on_globals_closure (GdkWaylandDisplay *display_wayland, OnHasGlobalsClosure *closure) { display_wayland->on_has_globals_closures = g_list_append (display_wayland->on_has_globals_closures, closure); } #ifdef G_ENABLE_DEBUG static const char * get_format_name (enum wl_shm_format format) { int i; #define FORMAT(s) { WL_SHM_FORMAT_ ## s, #s } struct { int format; const char *name; } formats[] = { FORMAT(ARGB8888), FORMAT(XRGB8888), FORMAT(C8), FORMAT(RGB332), FORMAT(BGR233), FORMAT(XRGB4444), FORMAT(XBGR4444), FORMAT(RGBX4444), FORMAT(BGRX4444), FORMAT(ARGB4444), FORMAT(ABGR4444), FORMAT(RGBA4444), FORMAT(BGRA4444), FORMAT(XRGB1555), FORMAT(XBGR1555), FORMAT(RGBX5551), FORMAT(BGRX5551), FORMAT(ARGB1555), FORMAT(ABGR1555), FORMAT(RGBA5551), FORMAT(BGRA5551), FORMAT(RGB565), FORMAT(BGR565), FORMAT(RGB888), FORMAT(BGR888), FORMAT(XBGR8888), FORMAT(RGBX8888), FORMAT(BGRX8888), FORMAT(ABGR8888), FORMAT(RGBA8888), FORMAT(BGRA8888), FORMAT(XRGB2101010), FORMAT(XBGR2101010), FORMAT(RGBX1010102), FORMAT(BGRX1010102), FORMAT(ARGB2101010), FORMAT(ABGR2101010), FORMAT(RGBA1010102), FORMAT(BGRA1010102), FORMAT(YUYV), FORMAT(YVYU), FORMAT(UYVY), FORMAT(VYUY), FORMAT(AYUV), FORMAT(NV12), FORMAT(NV21), FORMAT(NV16), FORMAT(NV61), FORMAT(YUV410), FORMAT(YVU410), FORMAT(YUV411), FORMAT(YVU411), FORMAT(YUV420), FORMAT(YVU420), FORMAT(YUV422), FORMAT(YVU422), FORMAT(YUV444), FORMAT(YVU444), { 0xffffffff, NULL } }; #undef FORMAT for (i = 0; formats[i].name; i++) { if (formats[i].format == format) return formats[i].name; } return NULL; } #endif static void wl_shm_format (void *data, struct wl_shm *wl_shm, uint32_t format) { GDK_NOTE (MISC, g_message ("supported pixel format %s", get_format_name (format))); } static const struct wl_shm_listener wl_shm_listener = { wl_shm_format }; static void server_decoration_manager_default_mode (void *data, struct org_kde_kwin_server_decoration_manager *manager, uint32_t mode) { g_assert (mode <= ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER); const char *modes[] = { [ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_NONE] = "none", [ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_CLIENT] = "client", [ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER] = "server", }; GdkWaylandDisplay *display_wayland = data; g_debug ("Compositor prefers decoration mode '%s'", modes[mode]); display_wayland->server_decoration_mode = mode; } static const struct org_kde_kwin_server_decoration_manager_listener server_decoration_listener = { .default_mode = server_decoration_manager_default_mode }; gboolean gdk_wayland_display_prefers_ssd (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); if (display_wayland->server_decoration_manager) return display_wayland->server_decoration_mode == ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_SERVER; return FALSE; } static void gdk_wayland_display_set_has_gtk_shell (GdkWaylandDisplay *display_wayland); static void gdk_wayland_display_add_output (GdkWaylandDisplay *display_wayland, guint32 id, struct wl_output *output, guint32 version); static void gdk_wayland_display_remove_output (GdkWaylandDisplay *display_wayland, guint32 id); static void gdk_registry_handle_global (void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { GdkWaylandDisplay *display_wayland = data; struct wl_output *output; GDK_NOTE (MISC, g_message ("add global %u, interface %s, version %u", id, interface, version)); if (strcmp (interface, "wl_compositor") == 0) { display_wayland->compositor = wl_registry_bind (display_wayland->wl_registry, id, &wl_compositor_interface, MIN (version, 3)); display_wayland->compositor_version = MIN (version, 3); } else if (strcmp (interface, "wl_shm") == 0) { display_wayland->shm = wl_registry_bind (display_wayland->wl_registry, id, &wl_shm_interface, 1); wl_shm_add_listener (display_wayland->shm, &wl_shm_listener, display_wayland); } else if (strcmp (interface, "zxdg_shell_v6") == 0) { display_wayland->xdg_shell = wl_registry_bind (display_wayland->wl_registry, id, &zxdg_shell_v6_interface, 1); zxdg_shell_v6_add_listener (display_wayland->xdg_shell, &xdg_shell_listener, display_wayland); } else if (strcmp (interface, "gtk_shell1") == 0) { display_wayland->gtk_shell = wl_registry_bind (display_wayland->wl_registry, id, >k_shell1_interface, MIN (version, GTK_SHELL1_VERSION)); gdk_wayland_display_set_has_gtk_shell (display_wayland); display_wayland->gtk_shell_version = version; } else if (strcmp (interface, "wl_output") == 0) { output = wl_registry_bind (display_wayland->wl_registry, id, &wl_output_interface, MIN (version, 2)); gdk_wayland_display_add_output (display_wayland, id, output, MIN (version, 2)); _gdk_wayland_display_async_roundtrip (display_wayland); } else if (strcmp (interface, "wl_seat") == 0) { static const char *required_device_manager_globals[] = { "wl_compositor", "wl_data_device_manager", NULL }; if (has_required_globals (display_wayland, required_device_manager_globals)) _gdk_wayland_display_add_seat (display_wayland, id, version); else { SeatAddedClosure *closure; closure = g_new0 (SeatAddedClosure, 1); closure->base.handler = seat_added_closure_run; closure->base.required_globals = required_device_manager_globals; closure->id = id; closure->version = version; postpone_on_globals_closure (display_wayland, &closure->base); } } else if (strcmp (interface, "wl_data_device_manager") == 0) { display_wayland->data_device_manager_version = MIN (version, 3); display_wayland->data_device_manager = wl_registry_bind (display_wayland->wl_registry, id, &wl_data_device_manager_interface, display_wayland->data_device_manager_version); } else if (strcmp (interface, "wl_subcompositor") == 0) { display_wayland->subcompositor = wl_registry_bind (display_wayland->wl_registry, id, &wl_subcompositor_interface, 1); } else if (strcmp (interface, "zwp_pointer_gestures_v1") == 0 && version == GDK_ZWP_POINTER_GESTURES_V1_VERSION) { display_wayland->pointer_gestures = wl_registry_bind (display_wayland->wl_registry, id, &zwp_pointer_gestures_v1_interface, version); } else if (strcmp (interface, "gtk_primary_selection_device_manager") == 0) { display_wayland->primary_selection_manager = wl_registry_bind(display_wayland->wl_registry, id, >k_primary_selection_device_manager_interface, 1); } else if (strcmp (interface, "zwp_tablet_manager_v2") == 0) { display_wayland->tablet_manager = wl_registry_bind(display_wayland->wl_registry, id, &zwp_tablet_manager_v2_interface, 1); } else if (strcmp (interface, "zxdg_exporter_v1") == 0) { display_wayland->xdg_exporter = wl_registry_bind (display_wayland->wl_registry, id, &zxdg_exporter_v1_interface, 1); } else if (strcmp (interface, "zxdg_importer_v1") == 0) { display_wayland->xdg_importer = wl_registry_bind (display_wayland->wl_registry, id, &zxdg_importer_v1_interface, 1); } else if (strcmp (interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) { display_wayland->keyboard_shortcuts_inhibit = wl_registry_bind (display_wayland->wl_registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); } else if (strcmp (interface, "org_kde_kwin_server_decoration_manager") == 0) { display_wayland->server_decoration_manager = wl_registry_bind (display_wayland->wl_registry, id, &org_kde_kwin_server_decoration_manager_interface, 1); org_kde_kwin_server_decoration_manager_add_listener (display_wayland->server_decoration_manager, &server_decoration_listener, display_wayland); } g_hash_table_insert (display_wayland->known_globals, GUINT_TO_POINTER (id), g_strdup (interface)); process_on_globals_closures (display_wayland); } static void gdk_registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t id) { GdkWaylandDisplay *display_wayland = data; GDK_NOTE (MISC, g_message ("remove global %u", id)); _gdk_wayland_display_remove_seat (display_wayland, id); gdk_wayland_display_remove_output (display_wayland, id); g_hash_table_remove (display_wayland->known_globals, GUINT_TO_POINTER (id)); /* FIXME: the object needs to be destroyed here, we're leaking */ } static const struct wl_registry_listener registry_listener = { gdk_registry_handle_global, gdk_registry_handle_global_remove }; G_GNUC_PRINTF (1, 0) static void log_handler (const char *format, va_list args) { g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args); } static void load_cursor_theme_closure_run (GdkWaylandDisplay *display_wayland, OnHasGlobalsClosure *closure) { _gdk_wayland_display_load_cursor_theme (display_wayland); } static void _gdk_wayland_display_prepare_cursor_themes (GdkWaylandDisplay *display_wayland) { OnHasGlobalsClosure *closure; static const char *required_cursor_theme_globals[] = { "wl_shm", NULL }; closure = g_new0 (OnHasGlobalsClosure, 1); closure->handler = load_cursor_theme_closure_run; closure->required_globals = required_cursor_theme_globals; postpone_on_globals_closure (display_wayland, closure); } static void init_settings (GdkDisplay *display); GdkDisplay * _gdk_wayland_display_open (const gchar *display_name) { struct wl_display *wl_display; GdkDisplay *display; GdkWaylandDisplay *display_wayland; GDK_NOTE (MISC, g_message ("opening display %s", display_name ? display_name : "")); /* If this variable is unset then wayland initialisation will surely * fail, logging a fatal error in the process. Save ourselves from * that. */ if (g_getenv ("XDG_RUNTIME_DIR") == NULL) return NULL; wl_log_set_handler_client (log_handler); wl_display = wl_display_connect (display_name); if (!wl_display) return NULL; display = g_object_new (GDK_TYPE_WAYLAND_DISPLAY, NULL); display_wayland = GDK_WAYLAND_DISPLAY (display); display_wayland->wl_display = wl_display; display_wayland->event_source = _gdk_wayland_display_event_source_new (display); init_settings (display); display_wayland->known_globals = g_hash_table_new_full (NULL, NULL, NULL, g_free); _gdk_wayland_display_init_cursors (display_wayland); _gdk_wayland_display_prepare_cursor_themes (display_wayland); display_wayland->wl_registry = wl_display_get_registry (display_wayland->wl_display); wl_registry_add_listener (display_wayland->wl_registry, ®istry_listener, display_wayland); _gdk_wayland_display_async_roundtrip (display_wayland); /* Wait for initializing to complete. This means waiting for all * asynchrounous roundtrips that were triggered during initial roundtrip. */ while (display_wayland->async_roundtrips != NULL) { if (wl_display_dispatch (display_wayland->wl_display) < 0) { g_object_unref (display); return NULL; } } /* Make sure we have xdg_shell at least */ if (display_wayland->xdg_shell == NULL) { g_warning ("Wayland compositor does not support xdg_shell interface," " not using Wayland display"); g_object_unref (display); return NULL; } display_wayland->selection = gdk_wayland_selection_new (); gdk_display_emit_opened (display); return display; } static void destroy_toplevel (gpointer data) { _gdk_window_destroy (GDK_WINDOW (data), FALSE); } static void gdk_wayland_display_dispose (GObject *object) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (object); g_list_free_full (display_wayland->toplevels, destroy_toplevel); if (display_wayland->event_source) { g_source_destroy (display_wayland->event_source); g_source_unref (display_wayland->event_source); display_wayland->event_source = NULL; } if (display_wayland->selection) { gdk_wayland_selection_free (display_wayland->selection); display_wayland->selection = NULL; } g_list_free_full (display_wayland->async_roundtrips, (GDestroyNotify) wl_callback_destroy); if (display_wayland->known_globals) { g_hash_table_destroy (display_wayland->known_globals); display_wayland->known_globals = NULL; } g_list_free_full (display_wayland->on_has_globals_closures, g_free); G_OBJECT_CLASS (gdk_wayland_display_parent_class)->dispose (object); } static void gdk_wayland_display_finalize (GObject *object) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (object); guint i; _gdk_wayland_display_finalize_cursors (display_wayland); g_free (display_wayland->startup_notification_id); g_free (display_wayland->cursor_theme_name); xkb_context_unref (display_wayland->xkb_context); for (i = 0; i < GDK_WAYLAND_THEME_SCALES_COUNT; i++) { if (display_wayland->scaled_cursor_themes[i]) { wl_cursor_theme_destroy (display_wayland->scaled_cursor_themes[i]); display_wayland->scaled_cursor_themes[i] = NULL; } } g_ptr_array_free (display_wayland->monitors, TRUE); g_hash_table_destroy (display_wayland->settings); G_OBJECT_CLASS (gdk_wayland_display_parent_class)->finalize (object); } static const gchar * gdk_wayland_display_get_name (GdkDisplay *display) { const gchar *name; name = g_getenv ("WAYLAND_DISPLAY"); if (name == NULL) name = "wayland-0"; return name; } void gdk_wayland_display_system_bell (GdkDisplay *display, GdkWindow *window) { GdkWaylandDisplay *display_wayland; struct gtk_surface1 *gtk_surface; gint64 now_ms; g_return_if_fail (GDK_IS_DISPLAY (display)); display_wayland = GDK_WAYLAND_DISPLAY (display); if (!display_wayland->gtk_shell) return; if (window) gtk_surface = gdk_wayland_window_get_gtk_surface (window); else gtk_surface = NULL; now_ms = g_get_monotonic_time () / 1000; if (now_ms - display_wayland->last_bell_time_ms < MIN_SYSTEM_BELL_DELAY_MS) return; display_wayland->last_bell_time_ms = now_ms; gtk_shell1_system_bell (display_wayland->gtk_shell, gtk_surface); } static void gdk_wayland_display_beep (GdkDisplay *display) { gdk_wayland_display_system_bell (display, NULL); } static void gdk_wayland_display_sync (GdkDisplay *display) { GdkWaylandDisplay *display_wayland; g_return_if_fail (GDK_IS_DISPLAY (display)); display_wayland = GDK_WAYLAND_DISPLAY (display); wl_display_roundtrip (display_wayland->wl_display); } static void gdk_wayland_display_flush (GdkDisplay *display) { g_return_if_fail (GDK_IS_DISPLAY (display)); if (!display->closed) wl_display_flush (GDK_WAYLAND_DISPLAY (display)->wl_display); } static void gdk_wayland_display_make_default (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); const gchar *startup_id; g_free (display_wayland->startup_notification_id); display_wayland->startup_notification_id = NULL; startup_id = g_getenv ("DESKTOP_STARTUP_ID"); if (startup_id && *startup_id != '\0') { if (!g_utf8_validate (startup_id, -1, NULL)) g_warning ("DESKTOP_STARTUP_ID contains invalid UTF-8"); else display_wayland->startup_notification_id = g_strdup (startup_id); /* Clear the environment variable so it won't be inherited by * child processes and confuse things. */ g_unsetenv ("DESKTOP_STARTUP_ID"); } } static gboolean gdk_wayland_display_has_pending (GdkDisplay *display) { return FALSE; } static GdkWindow * gdk_wayland_display_get_default_group (GdkDisplay *display) { g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); return NULL; } static gboolean gdk_wayland_display_supports_shapes (GdkDisplay *display) { return FALSE; } static gboolean gdk_wayland_display_supports_input_shapes (GdkDisplay *display) { return TRUE; } static gulong gdk_wayland_display_get_next_serial (GdkDisplay *display) { static gulong serial = 0; return ++serial; } /** * gdk_wayland_display_set_startup_notification_id: * @display: (type GdkWaylandDisplay): a #GdkDisplay * @startup_id: the startup notification ID (must be valid utf8) * * Sets the startup notification ID for a display. * * This is usually taken from the value of the DESKTOP_STARTUP_ID * environment variable, but in some cases (such as the application not * being launched using exec()) it can come from other sources. * * The startup ID is also what is used to signal that the startup is * complete (for example, when opening a window or when calling * gdk_notify_startup_complete()). **/ void gdk_wayland_display_set_startup_notification_id (GdkDisplay *display, const char *startup_id) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); g_free (display_wayland->startup_notification_id); display_wayland->startup_notification_id = g_strdup (startup_id); } static void gdk_wayland_display_notify_startup_complete (GdkDisplay *display, const gchar *startup_id) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); gchar *free_this = NULL; if (startup_id == NULL) { startup_id = free_this = display_wayland->startup_notification_id; display_wayland->startup_notification_id = NULL; if (startup_id == NULL) return; } if (display_wayland->gtk_shell) gtk_shell1_set_startup_id (display_wayland->gtk_shell, startup_id); g_free (free_this); } static GdkKeymap * _gdk_wayland_display_get_keymap (GdkDisplay *display) { GdkDevice *core_keyboard = NULL; static GdkKeymap *tmp_keymap = NULL; core_keyboard = gdk_seat_get_keyboard (gdk_display_get_default_seat (display)); if (core_keyboard && tmp_keymap) { g_object_unref (tmp_keymap); tmp_keymap = NULL; } if (core_keyboard) return _gdk_wayland_device_get_keymap (core_keyboard); if (!tmp_keymap) tmp_keymap = _gdk_wayland_keymap_new (display); return tmp_keymap; } static int gdk_wayland_display_get_n_monitors (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); return display_wayland->monitors->len; } static GdkMonitor * gdk_wayland_display_get_monitor (GdkDisplay *display, int monitor_num) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); if (monitor_num < 0 || monitor_num >= display_wayland->monitors->len) return NULL; return (GdkMonitor *)display_wayland->monitors->pdata[monitor_num]; } static GdkMonitor * gdk_wayland_display_get_monitor_at_window (GdkDisplay *display, GdkWindow *window) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); struct wl_output *output; int i; g_return_val_if_fail (GDK_IS_WAYLAND_WINDOW (window), NULL); output = gdk_wayland_window_get_wl_output (window); if (output == NULL) return NULL; for (i = 0; i < display_wayland->monitors->len; i++) { GdkMonitor *monitor = display_wayland->monitors->pdata[i]; if (gdk_wayland_monitor_get_wl_output (monitor) == output) return monitor; } return NULL; } static gboolean gdk_wayland_display_get_setting (GdkDisplay *display, const char *name, GValue *value); static void gdk_wayland_display_class_init (GdkWaylandDisplayClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GdkDisplayClass *display_class = GDK_DISPLAY_CLASS (class); object_class->dispose = gdk_wayland_display_dispose; object_class->finalize = gdk_wayland_display_finalize; display_class->window_type = gdk_wayland_window_get_type (); #ifdef GDK_RENDERING_VULKAN display_class->vk_context_type = GDK_TYPE_WAYLAND_VULKAN_CONTEXT; display_class->vk_extension_name = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; #endif display_class->get_name = gdk_wayland_display_get_name; display_class->beep = gdk_wayland_display_beep; display_class->sync = gdk_wayland_display_sync; display_class->flush = gdk_wayland_display_flush; display_class->make_default = gdk_wayland_display_make_default; display_class->has_pending = gdk_wayland_display_has_pending; display_class->queue_events = _gdk_wayland_display_queue_events; display_class->get_default_group = gdk_wayland_display_get_default_group; display_class->supports_shapes = gdk_wayland_display_supports_shapes; display_class->supports_input_shapes = gdk_wayland_display_supports_input_shapes; display_class->get_app_launch_context = _gdk_wayland_display_get_app_launch_context; display_class->get_next_serial = gdk_wayland_display_get_next_serial; display_class->notify_startup_complete = gdk_wayland_display_notify_startup_complete; display_class->create_window_impl = _gdk_wayland_display_create_window_impl; display_class->get_keymap = _gdk_wayland_display_get_keymap; display_class->text_property_to_utf8_list = _gdk_wayland_display_text_property_to_utf8_list; display_class->utf8_to_string_target = _gdk_wayland_display_utf8_to_string_target; display_class->make_gl_context_current = gdk_wayland_display_make_gl_context_current; display_class->get_n_monitors = gdk_wayland_display_get_n_monitors; display_class->get_monitor = gdk_wayland_display_get_monitor; display_class->get_monitor_at_window = gdk_wayland_display_get_monitor_at_window; display_class->get_setting = gdk_wayland_display_get_setting; display_class->set_cursor_theme = gdk_wayland_display_set_cursor_theme; } static void gdk_wayland_display_init (GdkWaylandDisplay *display) { display->xkb_context = xkb_context_new (0); display->monitors = g_ptr_array_new_with_free_func (g_object_unref); } GList * gdk_wayland_display_get_toplevel_windows (GdkDisplay *display) { return GDK_WAYLAND_DISPLAY (display)->toplevels; } void gdk_wayland_display_set_cursor_theme (GdkDisplay *display, const gchar *name, gint size) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY(display); struct wl_cursor_theme *theme; int i; g_assert (display_wayland); g_assert (display_wayland->shm); if (g_strcmp0 (name, display_wayland->cursor_theme_name) == 0 && display_wayland->cursor_theme_size == size) return; theme = wl_cursor_theme_load (name, size, display_wayland->shm); if (theme == NULL) { g_warning ("Failed to load cursor theme %s", name); return; } for (i = 0; i < GDK_WAYLAND_THEME_SCALES_COUNT; i++) { if (display_wayland->scaled_cursor_themes[i]) { wl_cursor_theme_destroy (display_wayland->scaled_cursor_themes[i]); display_wayland->scaled_cursor_themes[i] = NULL; } } display_wayland->scaled_cursor_themes[0] = theme; if (display_wayland->cursor_theme_name != NULL) g_free (display_wayland->cursor_theme_name); display_wayland->cursor_theme_name = g_strdup (name); display_wayland->cursor_theme_size = size; } struct wl_cursor_theme * _gdk_wayland_display_get_scaled_cursor_theme (GdkWaylandDisplay *display_wayland, guint scale) { struct wl_cursor_theme *theme; g_assert (display_wayland->cursor_theme_name); g_assert (scale <= GDK_WAYLAND_MAX_THEME_SCALE); g_assert (scale >= 1); theme = display_wayland->scaled_cursor_themes[scale - 1]; if (!theme) { theme = wl_cursor_theme_load (display_wayland->cursor_theme_name, display_wayland->cursor_theme_size * scale, display_wayland->shm); if (theme == NULL) { g_warning ("Failed to load cursor theme %s with scale %u", display_wayland->cursor_theme_name, scale); return NULL; } display_wayland->scaled_cursor_themes[scale - 1] = theme; } return theme; } static void _gdk_wayland_display_load_cursor_theme (GdkWaylandDisplay *display_wayland) { guint size; const gchar *name; GValue v = G_VALUE_INIT; g_assert (display_wayland); g_assert (display_wayland->shm); g_value_init (&v, G_TYPE_INT); if (gdk_display_get_setting (GDK_DISPLAY (display_wayland), "gtk-cursor-theme-size", &v)) size = g_value_get_int (&v); else size = 32; g_value_unset (&v); g_value_init (&v, G_TYPE_STRING); if (gdk_display_get_setting (GDK_DISPLAY (display_wayland), "gtk-cursor-theme-name", &v)) name = g_value_get_string (&v); else name = "default"; gdk_wayland_display_set_cursor_theme (GDK_DISPLAY (display_wayland), name, size); g_value_unset (&v); } guint32 _gdk_wayland_display_get_serial (GdkWaylandDisplay *display_wayland) { return display_wayland->serial; } void _gdk_wayland_display_update_serial (GdkWaylandDisplay *display_wayland, guint32 serial) { display_wayland->serial = serial; } /** * gdk_wayland_display_get_wl_display: * @display: (type GdkWaylandDisplay): a #GdkDisplay * * Returns the Wayland wl_display of a #GdkDisplay. * * Returns: (transfer none): a Wayland wl_display */ struct wl_display * gdk_wayland_display_get_wl_display (GdkDisplay *display) { g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), NULL); return GDK_WAYLAND_DISPLAY (display)->wl_display; } /** * gdk_wayland_display_get_wl_compositor: * @display: (type GdkWaylandDisplay): a #GdkDisplay * * Returns the Wayland global singleton compositor of a #GdkDisplay. * * Returns: (transfer none): a Wayland wl_compositor */ struct wl_compositor * gdk_wayland_display_get_wl_compositor (GdkDisplay *display) { g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), NULL); return GDK_WAYLAND_DISPLAY (display)->compositor; } static const cairo_user_data_key_t gdk_wayland_shm_surface_cairo_key; typedef struct _GdkWaylandCairoSurfaceData { gpointer buf; size_t buf_length; struct wl_shm_pool *pool; struct wl_buffer *buffer; GdkWaylandDisplay *display; uint32_t scale; } GdkWaylandCairoSurfaceData; static int open_shared_memory (void) { static gboolean force_shm_open = FALSE; int ret = -1; #if !defined (__NR_memfd_create) force_shm_open = TRUE; #endif do { #if defined (__NR_memfd_create) if (!force_shm_open) { ret = syscall (__NR_memfd_create, "gdk-wayland", MFD_CLOEXEC); /* fall back to shm_open until debian stops shipping 3.16 kernel * See bug 766341 */ if (ret < 0 && errno == ENOSYS) force_shm_open = TRUE; } #endif if (force_shm_open) { char name[NAME_MAX - 1] = ""; sprintf (name, "/gdk-wayland-%x", g_random_int ()); ret = shm_open (name, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600); if (ret >= 0) shm_unlink (name); else if (errno == EEXIST) continue; } } while (ret < 0 && errno == EINTR); if (ret < 0) g_critical (G_STRLOC ": creating shared memory file (using %s) failed: %m", force_shm_open? "shm_open" : "memfd_create"); return ret; } static struct wl_shm_pool * create_shm_pool (struct wl_shm *shm, int size, size_t *buf_length, void **data_out) { struct wl_shm_pool *pool; int fd; void *data; fd = open_shared_memory (); if (fd < 0) return NULL; if (ftruncate (fd, size) < 0) { g_critical (G_STRLOC ": Truncating shared memory file failed: %m"); close (fd); return NULL; } data = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { g_critical (G_STRLOC ": mmap'ping shared memory file failed: %m"); close (fd); return NULL; } pool = wl_shm_create_pool (shm, fd, size); close (fd); *data_out = data; *buf_length = size; return pool; } static void gdk_wayland_cairo_surface_destroy (void *p) { GdkWaylandCairoSurfaceData *data = p; if (data->buffer) wl_buffer_destroy (data->buffer); if (data->pool) wl_shm_pool_destroy (data->pool); munmap (data->buf, data->buf_length); g_free (data); } cairo_surface_t * _gdk_wayland_display_create_shm_surface (GdkWaylandDisplay *display, int width, int height, guint scale) { GdkWaylandCairoSurfaceData *data; cairo_surface_t *surface = NULL; cairo_status_t status; int stride; data = g_new (GdkWaylandCairoSurfaceData, 1); data->display = display; data->buffer = NULL; data->scale = scale; stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width*scale); data->pool = create_shm_pool (display->shm, height*scale*stride, &data->buf_length, &data->buf); surface = cairo_image_surface_create_for_data (data->buf, CAIRO_FORMAT_ARGB32, width*scale, height*scale, stride); data->buffer = wl_shm_pool_create_buffer (data->pool, 0, width*scale, height*scale, stride, WL_SHM_FORMAT_ARGB8888); cairo_surface_set_user_data (surface, &gdk_wayland_shm_surface_cairo_key, data, gdk_wayland_cairo_surface_destroy); cairo_surface_set_device_scale (surface, scale, scale); status = cairo_surface_status (surface); if (status != CAIRO_STATUS_SUCCESS) { g_critical (G_STRLOC ": Unable to create Cairo image surface: %s", cairo_status_to_string (status)); } return surface; } struct wl_buffer * _gdk_wayland_shm_surface_get_wl_buffer (cairo_surface_t *surface) { GdkWaylandCairoSurfaceData *data = cairo_surface_get_user_data (surface, &gdk_wayland_shm_surface_cairo_key); return data->buffer; } gboolean _gdk_wayland_is_shm_surface (cairo_surface_t *surface) { return cairo_surface_get_user_data (surface, &gdk_wayland_shm_surface_cairo_key) != NULL; } GdkWaylandSelection * gdk_wayland_display_get_selection (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); return display_wayland->selection; } typedef enum { GSD_FONT_ANTIALIASING_MODE_NONE, GSD_FONT_ANTIALIASING_MODE_GRAYSCALE, GSD_FONT_ANTIALIASING_MODE_RGBA } GsdFontAntialiasingMode; typedef enum { GSD_FONT_HINTING_NONE, GSD_FONT_HINTING_SLIGHT, GSD_FONT_HINTING_MEDIUM, GSD_FONT_HINTING_FULL } GsdFontHinting; typedef enum { GSD_FONT_RGBA_ORDER_RGBA, GSD_FONT_RGBA_ORDER_RGB, GSD_FONT_RGBA_ORDER_BGR, GSD_FONT_RGBA_ORDER_VRGB, GSD_FONT_RGBA_ORDER_VBGR } GsdFontRgbaOrder; static gdouble get_dpi_from_gsettings (GdkWaylandDisplay *display_wayland) { GSettings *settings; gdouble factor; settings = g_hash_table_lookup (display_wayland->settings, "org.gnome.desktop.interface"); if (settings != NULL) factor = g_settings_get_double (settings, "text-scaling-factor"); else factor = 1.0; return 96.0 * factor; } static void update_xft_settings (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); GSettings *settings; GsdFontAntialiasingMode antialiasing; GsdFontHinting hinting; GsdFontRgbaOrder order; gboolean use_rgba = FALSE; GsdXftSettings xft_settings; settings = g_hash_table_lookup (display_wayland->settings, "org.gnome.settings-daemon.plugins.xsettings"); if (settings) { antialiasing = g_settings_get_enum (settings, "antialiasing"); hinting = g_settings_get_enum (settings, "hinting"); order = g_settings_get_enum (settings, "rgba-order"); } else { antialiasing = GSD_FONT_ANTIALIASING_MODE_GRAYSCALE; hinting = GSD_FONT_HINTING_MEDIUM; order = GSD_FONT_RGBA_ORDER_RGB; } xft_settings.hinting = (hinting != GSD_FONT_HINTING_NONE); xft_settings.dpi = get_dpi_from_gsettings (display_wayland) * 1024; /* Xft wants 1/1024ths of an inch */ switch (hinting) { case GSD_FONT_HINTING_NONE: xft_settings.hintstyle = "hintnone"; break; case GSD_FONT_HINTING_SLIGHT: xft_settings.hintstyle = "hintslight"; break; case GSD_FONT_HINTING_MEDIUM: xft_settings.hintstyle = "hintmedium"; break; case GSD_FONT_HINTING_FULL: default: xft_settings.hintstyle = "hintfull"; break; } switch (order) { case GSD_FONT_RGBA_ORDER_RGBA: xft_settings.rgba = "rgba"; break; default: case GSD_FONT_RGBA_ORDER_RGB: xft_settings.rgba = "rgb"; break; case GSD_FONT_RGBA_ORDER_BGR: xft_settings.rgba = "bgr"; break; case GSD_FONT_RGBA_ORDER_VRGB: xft_settings.rgba = "vrgb"; break; case GSD_FONT_RGBA_ORDER_VBGR: xft_settings.rgba = "vbgr"; break; } switch (antialiasing) { default: case GSD_FONT_ANTIALIASING_MODE_NONE: xft_settings.antialias = FALSE; break; case GSD_FONT_ANTIALIASING_MODE_GRAYSCALE: xft_settings.antialias = TRUE; break; case GSD_FONT_ANTIALIASING_MODE_RGBA: xft_settings.antialias = TRUE; use_rgba = TRUE; } if (!use_rgba) xft_settings.rgba = "none"; if (display_wayland->xft_settings.antialias != xft_settings.antialias) { display_wayland->xft_settings.antialias = xft_settings.antialias; gdk_display_setting_changed (display, "gtk-xft-antialias"); } if (display_wayland->xft_settings.hinting != xft_settings.hinting) { display_wayland->xft_settings.hinting = xft_settings.hinting; gdk_display_setting_changed (display, "gtk-xft-hinting"); } if (display_wayland->xft_settings.hintstyle != xft_settings.hintstyle) { display_wayland->xft_settings.hintstyle = xft_settings.hintstyle; gdk_display_setting_changed (display, "gtk-xft-hintstyle"); } if (display_wayland->xft_settings.rgba != xft_settings.rgba) { display_wayland->xft_settings.rgba = xft_settings.rgba; gdk_display_setting_changed (display, "gtk-xft-rgba"); } if (display_wayland->xft_settings.dpi != xft_settings.dpi) { display_wayland->xft_settings.dpi = xft_settings.dpi; gdk_display_setting_changed (display, "gtk-xft-dpi"); } } #define WM_SETTINGS_SCHEMA "org.gnome.desktop.wm.preferences" #define CLASSIC_WM_SETTINGS_SCHEMA "org.gnome.shell.extensions.classic-overrides" typedef struct _TranslationEntry TranslationEntry; struct _TranslationEntry { gboolean valid; const gchar *schema; const gchar *key; const gchar *setting; GType type; union { const gchar *s; gint i; gboolean b; } fallback; }; static TranslationEntry translations[] = { { FALSE, "org.gnome.desktop.interface", "gtk-theme", "gtk-theme-name" , G_TYPE_STRING, { .s = "Adwaita" } }, { FALSE, "org.gnome.desktop.interface", "icon-theme", "gtk-icon-theme-name", G_TYPE_STRING, { .s = "gnome" } }, { FALSE, "org.gnome.desktop.interface", "cursor-theme", "gtk-cursor-theme-name", G_TYPE_STRING, { .s = "Adwaita" } }, { FALSE, "org.gnome.desktop.interface", "cursor-size", "gtk-cursor-theme-size", G_TYPE_INT, { .i = 32 } }, { FALSE, "org.gnome.desktop.interface", "font-name", "gtk-font-name", G_TYPE_STRING, { .s = "Cantarell 11" } }, { FALSE, "org.gnome.desktop.interface", "cursor-blink", "gtk-cursor-blink", G_TYPE_BOOLEAN, { .b = TRUE } }, { FALSE, "org.gnome.desktop.interface", "cursor-blink-time", "gtk-cursor-blink-time", G_TYPE_INT, { .i = 1200 } }, { FALSE, "org.gnome.desktop.interface", "cursor-blink-timeout", "gtk-cursor-blink-timeout", G_TYPE_INT, { .i = 3600 } }, { FALSE, "org.gnome.desktop.interface", "gtk-im-module", "gtk-im-module", G_TYPE_STRING, { .s = "simple" } }, { FALSE, "org.gnome.desktop.interface", "enable-animations", "gtk-enable-animations", G_TYPE_BOOLEAN, { .b = TRUE } }, { FALSE, "org.gnome.desktop.interface", "gtk-enable-primary-paste", "gtk-enable-primary-paste", G_TYPE_BOOLEAN, { .b = TRUE } }, { FALSE, "org.gnome.settings-daemon.peripherals.mouse", "double-click", "gtk-double-click-time", G_TYPE_INT, { .i = 400 } }, { FALSE, "org.gnome.settings-daemon.peripherals.mouse", "drag-threshold", "gtk-dnd-drag-threshold", G_TYPE_INT, {.i = 8 } }, { FALSE, "org.gnome.desktop.sound", "theme-name", "gtk-sound-theme-name", G_TYPE_STRING, { .s = "freedesktop" } }, { FALSE, "org.gnome.desktop.sound", "event-sounds", "gtk-enable-event-sounds", G_TYPE_BOOLEAN, { .b = TRUE } }, { FALSE, "org.gnome.desktop.sound", "input-feedback-sounds", "gtk-enable-input-feedback-sounds", G_TYPE_BOOLEAN, { . b = FALSE } }, { FALSE, "org.gnome.desktop.privacy", "recent-files-max-age", "gtk-recent-files-max-age", G_TYPE_INT, { .i = 30 } }, { FALSE, "org.gnome.desktop.privacy", "remember-recent-files", "gtk-recent-files-enabled", G_TYPE_BOOLEAN, { .b = TRUE } }, { FALSE, WM_SETTINGS_SCHEMA, "button-layout", "gtk-decoration-layout", G_TYPE_STRING, { .s = "menu:close" } }, { FALSE, CLASSIC_WM_SETTINGS_SCHEMA, "button-layout", "gtk-decoration-layout", G_TYPE_STRING, { .s = "menu:close" } }, { FALSE, "org.gnome.settings-daemon.plugins.xsettings", "antialiasing", "gtk-xft-antialias", G_TYPE_NONE, { .i = 0 } }, { FALSE, "org.gnome.settings-daemon.plugins.xsettings", "hinting", "gtk-xft-hinting", G_TYPE_NONE, { .i = 0 } }, { FALSE, "org.gnome.settings-daemon.plugins.xsettings", "hinting", "gtk-xft-hintstyle", G_TYPE_NONE, { .i = 0 } }, { FALSE, "org.gnome.settings-daemon.plugins.xsettings", "rgba-order", "gtk-xft-rgba", G_TYPE_NONE, { .i = 0 } }, { FALSE, "org.gnome.desktop.interface", "text-scaling-factor", "gtk-xft-dpi" , G_TYPE_NONE, { .i = 0 } }, { FALSE, "org.gnome.desktop.wm.preferences", "action-double-click-titlebar", "gtk-titlebar-double-click", G_TYPE_STRING, { .s = "toggle-maximize" } }, { FALSE, "org.gnome.desktop.wm.preferences", "action-middle-click-titlebar", "gtk-titlebar-middle-click", G_TYPE_STRING, { .s = "none" } }, { FALSE, "org.gnome.desktop.wm.preferences", "action-right-click-titlebar", "gtk-titlebar-right-click", G_TYPE_STRING, { .s = "menu" } }, { FALSE, "org.gnome.desktop.a11y", "always-show-text-caret", "gtk-keynav-use-caret", G_TYPE_BOOLEAN, { .b = FALSE } } }; static TranslationEntry * find_translation_entry_by_key (GSettings *settings, const char *key) { guint i; char *schema; g_object_get (settings, "schema", &schema, NULL); for (i = 0; i < G_N_ELEMENTS (translations); i++) { if (g_str_equal (schema, translations[i].schema) && g_str_equal (key, translations[i].key)) { g_free (schema); return &translations[i]; } } g_free (schema); return NULL; } static TranslationEntry * find_translation_entry_by_setting (const char *setting) { guint i; for (i = 0; i < G_N_ELEMENTS (translations); i++) { if (g_str_equal (setting, translations[i].setting)) return &translations[i]; } return NULL; } static void settings_changed (GSettings *settings, const char *key, GdkDisplay *display) { TranslationEntry *entry; entry = find_translation_entry_by_key (settings, key); if (entry != NULL) { if (entry->type != G_TYPE_NONE) gdk_display_setting_changed (display, entry->setting); else update_xft_settings (display); } } static void init_settings (GdkDisplay *display) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); GSettingsSchemaSource *source; GSettingsSchema *schema; GSettings *settings; gint i; g_intern_static_string ("antialiasing"); g_intern_static_string ("hinting"); g_intern_static_string ("rgba-order"); g_intern_static_string ("text-scaling-factor"); display_wayland->settings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); source = g_settings_schema_source_get_default (); if (source == NULL) return; for (i = 0; i < G_N_ELEMENTS (translations); i++) { schema = g_settings_schema_source_lookup (source, translations[i].schema, TRUE); if (!schema) continue; g_intern_static_string (translations[i].key); if (g_hash_table_lookup (display_wayland->settings, (gpointer)translations[i].schema) == NULL) { settings = g_settings_new_full (schema, NULL, NULL); g_signal_connect (settings, "changed", G_CALLBACK (settings_changed), display); g_hash_table_insert (display_wayland->settings, (gpointer)translations[i].schema, settings); } if (g_settings_schema_has_key (schema, translations[i].key)) translations[i].valid = TRUE; g_settings_schema_unref (schema); } update_xft_settings (display); } static void gtk_shell_handle_capabilities (void *data, struct gtk_shell1 *shell, uint32_t capabilities) { GdkDisplay *display = data; GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (data); display_wayland->shell_capabilities = capabilities; gdk_display_setting_changed (display, "gtk-shell-shows-app-menu"); gdk_display_setting_changed (display, "gtk-shell-shows-menubar"); gdk_display_setting_changed (display, "gtk-shell-shows-desktop"); } struct gtk_shell1_listener gdk_display_gtk_shell_listener = { gtk_shell_handle_capabilities }; static void gdk_wayland_display_set_has_gtk_shell (GdkWaylandDisplay *display_wayland) { gtk_shell1_add_listener (display_wayland->gtk_shell, &gdk_display_gtk_shell_listener, display_wayland); } static void set_value_from_entry (GdkDisplay *display, TranslationEntry *entry, GValue *value) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); GSettings *settings; settings = (GSettings *)g_hash_table_lookup (display_wayland->settings, entry->schema); switch (entry->type) { case G_TYPE_STRING: if (settings && entry->valid) { gchar *s; s = g_settings_get_string (settings, entry->key); g_value_set_string (value, s); g_free (s); } else { g_value_set_static_string (value, entry->fallback.s); } break; case G_TYPE_INT: g_value_set_int (value, settings && entry->valid ? g_settings_get_int (settings, entry->key) : entry->fallback.i); break; case G_TYPE_BOOLEAN: g_value_set_boolean (value, settings && entry->valid ? g_settings_get_boolean (settings, entry->key) : entry->fallback.b); break; case G_TYPE_NONE: if (g_str_equal (entry->setting, "gtk-xft-antialias")) g_value_set_int (value, display_wayland->xft_settings.antialias); else if (g_str_equal (entry->setting, "gtk-xft-hinting")) g_value_set_int (value, display_wayland->xft_settings.hinting); else if (g_str_equal (entry->setting, "gtk-xft-hintstyle")) g_value_set_static_string (value, display_wayland->xft_settings.hintstyle); else if (g_str_equal (entry->setting, "gtk-xft-rgba")) g_value_set_static_string (value, display_wayland->xft_settings.rgba); else if (g_str_equal (entry->setting, "gtk-xft-dpi")) g_value_set_int (value, display_wayland->xft_settings.dpi); else g_assert_not_reached (); break; default: g_assert_not_reached (); } } static void set_decoration_layout_from_entry (GdkDisplay *display, TranslationEntry *entry, GValue *value) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); GSettings *settings = NULL; const char *session; /* Hack: until we get session-dependent defaults in GSettings, * swap out the usual schema for the "classic" one when * running in classic mode */ session = g_getenv ("XDG_CURRENT_DESKTOP"); if (session && strstr (session, "GNOME-Classic")) settings = (GSettings *)g_hash_table_lookup (display_wayland->settings, CLASSIC_WM_SETTINGS_SCHEMA); if (settings == NULL) settings = (GSettings *)g_hash_table_lookup (display_wayland->settings, WM_SETTINGS_SCHEMA); if (settings) { gchar *s = g_settings_get_string (settings, entry->key); translate_wm_button_layout_to_gtk (s); g_value_set_string (value, s); g_free (s); } else { g_value_set_static_string (value, entry->fallback.s); } } static gboolean set_capability_setting (GdkDisplay *display, GValue *value, enum gtk_shell1_capability test) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); g_value_set_boolean (value, (display_wayland->shell_capabilities & test) == test); return TRUE; } static gboolean gdk_wayland_display_get_setting (GdkDisplay *display, const char *name, GValue *value) { TranslationEntry *entry; if (g_hash_table_size (GDK_WAYLAND_DISPLAY (display)->settings) == 0) return FALSE; entry = find_translation_entry_by_setting (name); if (entry != NULL) { if (strcmp (name, "gtk-decoration-layout") == 0) set_decoration_layout_from_entry (display, entry, value); else set_value_from_entry (display, entry, value); return TRUE; } if (strcmp (name, "gtk-shell-shows-app-menu") == 0) return set_capability_setting (display, value, GTK_SHELL1_CAPABILITY_GLOBAL_APP_MENU); if (strcmp (name, "gtk-shell-shows-menubar") == 0) return set_capability_setting (display, value, GTK_SHELL1_CAPABILITY_GLOBAL_MENU_BAR); if (strcmp (name, "gtk-shell-shows-desktop") == 0) return set_capability_setting (display, value, GTK_SHELL1_CAPABILITY_DESKTOP_ICONS); if (strcmp (name, "gtk-dialogs-use-header") == 0) { g_value_set_boolean (value, TRUE); return TRUE; } return FALSE; } #ifdef G_ENABLE_DEBUG static const char * subpixel_to_string (int layout) { int i; struct { int layout; const char *name; } layouts[] = { { WL_OUTPUT_SUBPIXEL_UNKNOWN, "unknown" }, { WL_OUTPUT_SUBPIXEL_NONE, "none" }, { WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, "rgb" }, { WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, "bgr" }, { WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, "vrgb" }, { WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, "vbgr" }, { 0xffffffff, NULL } }; for (i = 0; layouts[i].name; i++) { if (layouts[i].layout == layout) return layouts[i].name; } return NULL; } static const char * transform_to_string (int transform) { int i; struct { int transform; const char *name; } transforms[] = { { WL_OUTPUT_TRANSFORM_NORMAL, "normal" }, { WL_OUTPUT_TRANSFORM_90, "90" }, { WL_OUTPUT_TRANSFORM_180, "180" }, { WL_OUTPUT_TRANSFORM_270, "270" }, { WL_OUTPUT_TRANSFORM_FLIPPED, "flipped" }, { WL_OUTPUT_TRANSFORM_FLIPPED_90, "flipped 90" }, { WL_OUTPUT_TRANSFORM_FLIPPED_180, "flipped 180" }, { WL_OUTPUT_TRANSFORM_FLIPPED_270, "flipped 270" }, { 0xffffffff, NULL } }; for (i = 0; transforms[i].name; i++) { if (transforms[i].transform == transform) return transforms[i].name; } return NULL; } #endif static void update_scale (GdkDisplay *display) { g_list_foreach (gdk_wayland_display_get_toplevel_windows (display), (GFunc)gdk_wayland_window_update_scale, NULL); } static void output_handle_geometry (void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int32_t transform) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; GDK_NOTE (MISC, g_message ("handle geometry output %d, position %d %d, phys. size %d %d, subpixel layout %s, manufacturer %s, model %s, transform %s", monitor->id, x, y, physical_width, physical_height, subpixel_to_string (subpixel), make, model, transform_to_string (transform))); gdk_monitor_set_position (GDK_MONITOR (monitor), x, y); gdk_monitor_set_physical_size (GDK_MONITOR (monitor), physical_width, physical_height); gdk_monitor_set_subpixel_layout (GDK_MONITOR (monitor), subpixel); gdk_monitor_set_manufacturer (GDK_MONITOR (monitor), make); gdk_monitor_set_model (GDK_MONITOR (monitor), model); if (GDK_MONITOR (monitor)->geometry.width != 0 && monitor->version < OUTPUT_VERSION_WITH_DONE) update_scale (GDK_MONITOR (monitor)->display); } static void output_handle_done (void *data, struct wl_output *wl_output) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; GdkDisplay *display = gdk_monitor_get_display (GDK_MONITOR (monitor)); GDK_NOTE (MISC, g_message ("handle done output %d", monitor->id)); if (!monitor->added) { monitor->added = TRUE; g_ptr_array_add (GDK_WAYLAND_DISPLAY (display)->monitors, monitor); gdk_display_monitor_added (display, GDK_MONITOR (monitor)); } update_scale (display); } static void output_handle_scale (void *data, struct wl_output *wl_output, int32_t scale) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; GdkRectangle previous_geometry; int previous_scale; int width; int height; GDK_NOTE (MISC, g_message ("handle scale output %d, scale %d", monitor->id, scale)); gdk_monitor_get_geometry (GDK_MONITOR (monitor), &previous_geometry); previous_scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); width = previous_geometry.width * previous_scale; height = previous_geometry.height * previous_scale; gdk_monitor_set_scale_factor (GDK_MONITOR (monitor), scale); gdk_monitor_set_size (GDK_MONITOR (monitor), width / scale, height / scale); if (GDK_MONITOR (monitor)->geometry.width != 0 && monitor->version < OUTPUT_VERSION_WITH_DONE) update_scale (GDK_MONITOR (monitor)->display); } static void output_handle_mode (void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { GdkWaylandMonitor *monitor = (GdkWaylandMonitor *)data; int scale; GDK_NOTE (MISC, g_message ("handle mode output %d, size %d %d, rate %d", monitor->id, width, height, refresh)); if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) return; scale = gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); gdk_monitor_set_size (GDK_MONITOR (monitor), width / scale, height / scale); gdk_monitor_set_refresh_rate (GDK_MONITOR (monitor), refresh); if (width != 0 && monitor->version < OUTPUT_VERSION_WITH_DONE) update_scale (GDK_MONITOR (monitor)->display); } static const struct wl_output_listener output_listener = { output_handle_geometry, output_handle_mode, output_handle_done, output_handle_scale, }; static void gdk_wayland_display_add_output (GdkWaylandDisplay *display_wayland, guint32 id, struct wl_output *output, guint32 version) { GdkWaylandMonitor *monitor; monitor = g_object_new (GDK_TYPE_WAYLAND_MONITOR, "display", GDK_DISPLAY (display_wayland), NULL); monitor->id = id; monitor->output = output; monitor->version = version; if (monitor->version < OUTPUT_VERSION_WITH_DONE) { g_ptr_array_add (display_wayland->monitors, monitor); gdk_display_monitor_added (GDK_DISPLAY (display_wayland), GDK_MONITOR (monitor)); } wl_output_add_listener (output, &output_listener, monitor); } struct wl_output * gdk_wayland_display_get_wl_output (GdkDisplay *display, gint monitor_num) { GdkWaylandMonitor *monitor; monitor = GDK_WAYLAND_DISPLAY (display)->monitors->pdata[monitor_num]; return monitor->output; } static GdkWaylandMonitor * get_monitor_for_id (GdkWaylandDisplay *display_wayland, guint32 id) { int i; for (i = 0; i < display_wayland->monitors->len; i++) { GdkWaylandMonitor *monitor = display_wayland->monitors->pdata[i]; if (monitor->id == id) return monitor; } return NULL; } static GdkWaylandMonitor * get_monitor_for_output (GdkWaylandDisplay *display_wayland, struct wl_output *output) { int i; for (i = 0; i < display_wayland->monitors->len; i++) { GdkWaylandMonitor *monitor = display_wayland->monitors->pdata[i]; if (monitor->output == output) return monitor; } return NULL; } static void gdk_wayland_display_remove_output (GdkWaylandDisplay *display_wayland, guint32 id) { GdkWaylandMonitor *monitor; monitor = get_monitor_for_id (display_wayland, id); if (monitor != NULL) { g_object_ref (monitor); g_ptr_array_remove (display_wayland->monitors, monitor); gdk_display_monitor_removed (GDK_DISPLAY (display_wayland), GDK_MONITOR (monitor)); update_scale (GDK_DISPLAY (display_wayland)); g_object_unref (monitor); } } int gdk_wayland_display_get_output_refresh_rate (GdkWaylandDisplay *display_wayland, struct wl_output *output) { GdkWaylandMonitor *monitor; monitor = get_monitor_for_output (display_wayland, output); if (monitor != NULL) return gdk_monitor_get_refresh_rate (GDK_MONITOR (monitor)); return 0; } guint32 gdk_wayland_display_get_output_scale (GdkWaylandDisplay *display_wayland, struct wl_output *output) { GdkWaylandMonitor *monitor; monitor = get_monitor_for_output (display_wayland, output); if (monitor != NULL) return gdk_monitor_get_scale_factor (GDK_MONITOR (monitor)); return 0; } /** * gdk_wayland_display_query_registry: * @display: a wayland #GdkDisplay * @interface: global interface to query in the registry * * Returns %TRUE if the the interface was found in the display * wl_registry.global handler. * * Returns: %TRUE if the global is offered by the compositor * * Since: 3.94 **/ gboolean gdk_wayland_display_query_registry (GdkDisplay *display, const gchar *global) { GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); GHashTableIter iter; gchar *value; g_return_val_if_fail (GDK_IS_WAYLAND_DISPLAY (display), FALSE); g_return_val_if_fail (global != NULL, FALSE); g_hash_table_iter_init (&iter, display_wayland->known_globals); while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &value)) { if (strcmp (value, global) == 0) return TRUE; } return FALSE; }