/**
* This file is a part of the Cairo-Dock project
*
* Copyright : (C) see the 'copyright' file.
* E-mail    : see the 'copyright' file.
*
* 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 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "gldi-config.h"

#ifdef HAVE_WAYLAND

#include <poll.h>

#include <gdk/gdk.h>
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/gdkwayland.h>
#endif

#include <wayland-client.h>
#include <wayland-client-protocol.h>

#include "cairo-dock-struct.h"
#include "cairo-dock-utils.h"
#include "cairo-dock-log.h"
#include "cairo-dock-surface-factory.h"
#include "cairo-dock-applications-manager.h"  // myTaskbarParam.iMinimizedWindowRenderType
#include "cairo-dock-desktop-manager.h"
#include "cairo-dock-class-manager.h"  // gldi_class_startup_notify_end
#include "cairo-dock-windows-manager.h"
#include "cairo-dock-container-priv.h"  // GldiContainerManagerBackend
#include "cairo-dock-dock-factory.h" // struct _CairoDock
#include "cairo-dock-dock-manager.h" // myDockObjectMgr, needed for CAIRO_DOCK_IS_DOCK
#include "cairo-dock-icon-facility.h" // cairo_dock_get_icon_container
#include "cairo-dock-opengl.h"
#ifdef HAVE_WAYLAND_PROTOCOLS
#include "cairo-dock-foreign-toplevel.h"
#include "cairo-dock-plasma-window-manager.h"
#include "cairo-dock-cosmic-toplevel.h"
#include "cairo-dock-plasma-virtual-desktop.h"
#include "cairo-dock-ext-workspaces.h"
#endif
#include "cairo-dock-wayland-wm.h"
#include "cairo-dock-wayland-hotspots.h"
#include "cairo-dock-egl.h"
#define _MANAGER_DEF_
#include "cairo-dock-wayland-manager-priv.h"

#include "gldi-config.h"
#ifdef HAVE_GTK_LAYER_SHELL
#include <gtk-layer-shell.h>
#endif
gboolean g_bDisableLayerShell = FALSE;

// public (manager, config, data)
GldiManager myWaylandMgr;

// dependencies
extern GldiContainer *g_pPrimaryContainer;
extern gboolean g_bUseOpenGL;

// private
static struct wl_display *s_pDisplay = NULL;
static struct wl_compositor* s_pCompositor = NULL;
static gboolean s_bHave_Layer_Shell = FALSE;

static GldiWaylandCompositorType s_CompositorType = WAYLAND_COMPOSITOR_NONE;


static void _try_detect_compositor (void)
{
	const char *dmb_names = gldi_desktop_manager_get_backend_names ();
	if (dmb_names)
	{
		if (strstr (dmb_names, "Wayfire"))
			s_CompositorType = WAYLAND_COMPOSITOR_WAYFIRE;
		else if (strstr (dmb_names, "plasma"))
			s_CompositorType = WAYLAND_COMPOSITOR_KWIN;
		else if (strstr (dmb_names, "KWin"))
			s_CompositorType = WAYLAND_COMPOSITOR_KWIN;
		else if (strstr (dmb_names, "ext")) // will also match Labwc which implements the ext workspaces protocols
			s_CompositorType = WAYLAND_COMPOSITOR_GENERIC;
	}
}

GldiWaylandCompositorType gldi_wayland_get_compositor_type (void) { return s_CompositorType; }
void gldi_wayland_set_compositor_type (GldiWaylandCompositorType type) { s_CompositorType = type; }

CairoDockPositionType gldi_wayland_get_edge_for_dock (const CairoDock *pDock)
{
	CairoDockPositionType pos;
	if (pDock->container.bIsHorizontal == CAIRO_DOCK_HORIZONTAL)
		pos = pDock->container.bDirectionUp ? CAIRO_DOCK_BOTTOM : CAIRO_DOCK_TOP;
	else if (pDock->container.bIsHorizontal == CAIRO_DOCK_VERTICAL)
		pos = pDock->container.bDirectionUp ? CAIRO_DOCK_RIGHT : CAIRO_DOCK_LEFT;
	else pos = CAIRO_DOCK_INSIDE_SCREEN;
	return pos;
}

// Set the input shape directly for the container's wl_surface.
// This is necessary for some reason on Wayland + EGL.
// Note: gtk_widget_input_shape_combine_region() is already called
// in cairo-dock-container.c, we only need to deal with the
// Wayland-specific stuff here
static void _set_input_shape(GldiContainer *pContainer, cairo_region_t *pShape)
{
	// note: we only do this if this container is using EGL
#ifdef HAVE_EGL
	if (!pContainer->eglwindow) return;
	if (!s_pCompositor)
	{
		cd_warning ("wayland-manager: No valid Wayland compositor interface available!\n");
		return;
	}
	struct wl_surface *wls = gdk_wayland_window_get_wl_surface (
		gldi_container_get_gdk_window (pContainer));
	if (!wls)
	{
		cd_warning ("wayland-manager: cannot get wl_surface for container!\n");
		return;	
	}
	
	struct wl_region* r = NULL;
	if (pShape)
	{
		r = wl_compositor_create_region(s_pCompositor);
		int n = cairo_region_num_rectangles(pShape);
		int i;
		for (i = 0; i < n; i++)
		{
			cairo_rectangle_int_t rect;
			cairo_region_get_rectangle (pShape, i, &rect);
			wl_region_add (r, rect.x, rect.y, rect.width, rect.height);
		}
	}
	wl_surface_set_input_region (wls, r);
	if (r) wl_region_destroy (r);
#endif
}


#ifdef HAVE_GTK_LAYER_SHELL
/// reserve space for the dock; note: most parameters are ignored, only a size is calculated based on coordinates
static void _layer_shell_reserve_space (GldiContainer *pContainer, int left, int right, int top, int bottom,
		G_GNUC_UNUSED int left_start_y, G_GNUC_UNUSED int left_end_y, G_GNUC_UNUSED int right_start_y, G_GNUC_UNUSED int right_end_y,
		G_GNUC_UNUSED int top_start_x, G_GNUC_UNUSED int top_end_x, G_GNUC_UNUSED int bottom_start_x, G_GNUC_UNUSED int bottom_end_x)
{
	GtkWindow* window = GTK_WINDOW (pContainer->pWidget);
	gint r = (pContainer->bIsHorizontal) ? (top - bottom) : (right - left);
	if (r < 0) {
		r *= -1;
	}
	gtk_layer_set_exclusive_zone (window, r);
}

static void _set_keep_below (GldiContainer *pContainer, gboolean bKeepBelow)
{
	GtkWindow* window = GTK_WINDOW (pContainer->pWidget);
	gtk_layer_set_layer (window, bKeepBelow ? GTK_LAYER_SHELL_LAYER_BOTTOM : GTK_LAYER_SHELL_LAYER_TOP);
}

static void _layer_shell_init_for_window (GldiContainer *pContainer, const gchar *cNamespace)
{
	GtkWindow* window = GTK_WINDOW (pContainer->pWidget);
	if (gtk_window_get_transient_for (window))
	{
		// subdock or other surface with a parent
		// we need to call gdk_move_to_rect(), but specifically
		// (1) before the window is mapped, but (2) during it is realized
		// A dummy call to gdk_window_move_to_rect() causes the gtk-layer-shell
		// internals related to this window to be initialized properly (note:
		// this is important if the parent is a "proper" layer-shell window).
		// This _has_ to happen before the GtkWindow is mapped first, but also
		// has to happen after some initial setup. Doing this as a response to
		// a "realize" signal seems to be a good way, but there should be some
		// less hacky solution for this as well. Maybe I'm missing something here?
		// g_signal_connect (window, "realize", G_CALLBACK (sublayer_realize_cb), NULL);
		
		// gldi_container_move_to_rect() will ensure this
		GdkRectangle rect = {0, 0, 1, 1};
		gldi_container_move_to_rect (pContainer, &rect, GDK_GRAVITY_SOUTH,
			GDK_GRAVITY_NORTH_WEST, GDK_ANCHOR_SLIDE, 0, 0);
	}
	else
	{
		gtk_layer_init_for_window (window);
		// Note: to enable receiving any keyboard events, we need both the compositor
		// and gtk-layer-shell to support version >= 4 of the layer-shell protocol.
		// Here we test if we are compiling against a version of gtk-layer-shell that
		// has this functionality. Setting keyboard interactivity may still fail at
		// runtime if the compositor does not support this. In this case, the dock
		// will not receive keyboard events at all.
		// We do not set this on KWin, since newer versions will then include our
		// windows in tracking focus and send a deactivated event for the active
		// window when the dock is clicked (this will confuse our tracking of the
		// active window, see: https://github.com/Cairo-Dock/cairo-dock-core/issues/140).
		// Also, there seems to be no downside, as we still get keyboard modifiers
		// for clicks and popups also still get keyboard focus.
		if (s_CompositorType == WAYLAND_COMPOSITOR_UNKNOWN) _try_detect_compositor ();
		gtk_layer_set_keyboard_mode (window,
			(s_CompositorType == WAYLAND_COMPOSITOR_KWIN) ?
			GTK_LAYER_SHELL_KEYBOARD_MODE_NONE :
			GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND
		);
		gtk_layer_set_namespace (window, cNamespace ? cNamespace : "cairo-dock");
	}
}

static void _layer_shell_move_to_monitor (GldiContainer *pContainer, int iNumScreen)
{
	if (iNumScreen < 0) return;
	GtkWindow *window = GTK_WINDOW (pContainer->pWidget);
	// this only works for parent windows, not popups
	if (gtk_window_get_transient_for (window)) return;
	
	int iNumMonitors = 0;
	GdkMonitor *const *pMonitors = gldi_desktop_get_monitors (&iNumMonitors);
	GdkMonitor *monitor = (iNumScreen < iNumMonitors) ? pMonitors[iNumScreen] : NULL;
	if (monitor != gtk_layer_get_monitor (window)) gtk_layer_set_monitor (window, monitor);
}
#endif

static void _move_resize_dock (CairoDock *pDock)
{
	int iNewWidth = pDock->iMaxDockWidth;
	int iNewHeight = pDock->iMaxDockHeight;
	
	if (pDock->container.bIsHorizontal)
	{
		gdk_window_resize (gldi_container_get_gdk_window (CAIRO_CONTAINER (pDock)), iNewWidth, iNewHeight);
		if (g_bUseOpenGL) gldi_gl_container_resized (CAIRO_CONTAINER (pDock), iNewWidth, iNewHeight);
	}
	else
	{
		gdk_window_resize (gldi_container_get_gdk_window (CAIRO_CONTAINER (pDock)), iNewHeight, iNewWidth);
		if (g_bUseOpenGL) gldi_gl_container_resized (CAIRO_CONTAINER (pDock), iNewHeight, iNewWidth);
	}

#ifdef HAVE_GTK_LAYER_SHELL
	if (s_bHave_Layer_Shell)
	{
		GtkWindow* window = GTK_WINDOW (pDock->container.pWidget);
		if (gtk_window_get_transient_for (window)) return; // can only use this for main docks
		// Reset old anchors
		gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE);
		gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, FALSE);
		gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE);
		gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, FALSE);
		// Set new anchor
		CairoDockPositionType iScreenBorder = gldi_wayland_get_edge_for_dock (pDock);
		GtkLayerShellEdge anchor_edge;
		switch (iScreenBorder)
		{
			case CAIRO_DOCK_BOTTOM :
				anchor_edge = GTK_LAYER_SHELL_EDGE_BOTTOM;
				break;
			case CAIRO_DOCK_TOP :
				anchor_edge = GTK_LAYER_SHELL_EDGE_TOP;
				break;
			case CAIRO_DOCK_RIGHT :
				anchor_edge = GTK_LAYER_SHELL_EDGE_RIGHT;
				break;
			case CAIRO_DOCK_LEFT :
				anchor_edge = GTK_LAYER_SHELL_EDGE_LEFT;
				break;
			case CAIRO_DOCK_INSIDE_SCREEN :
			case CAIRO_DOCK_NB_POSITIONS :
			default :
				cd_warning ("Cannot determine which edge to anchor a dock to!");
				return;
		}
		
		gtk_layer_set_anchor (window, anchor_edge, TRUE);
		gtk_layer_set_margin (window, anchor_edge, pDock->iGapY);
	}
#endif
}

#ifdef HAVE_GTK_LAYER_SHELL
struct _KBData
{
	GtkWindow *window;
	guint iSidTimer;
};

static void _kb_mode_data_destroy (gpointer ptr, G_GNUC_UNUSED GObject* pObj)
{
	if (ptr)
	{
		struct _KBData *data = (struct _KBData*)ptr;
		if (data->iSidTimer) g_source_remove (data->iSidTimer);
		g_free (data);
	}
}

static gboolean _kb_mode_callback (gpointer ptr)
{
	if (ptr)
	{
		struct _KBData *data = (struct _KBData*)ptr;
		if (data->window)
		{
			gtk_layer_set_keyboard_mode (data->window, GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND);
			g_object_weak_unref (G_OBJECT (data->window), _kb_mode_data_destroy, ptr);
		}
		g_free (ptr);
	}
	return G_SOURCE_REMOVE;
}

static void _set_kb_mode_callback (GtkWindow *window)
{
	struct _KBData *data = g_new0 (struct _KBData, 1);
	data->window = window;
	// 100 ms should be enough for GTK to commit our previous change
	data->iSidTimer = g_timeout_add (100, _kb_mode_callback, data);
	g_object_weak_ref (G_OBJECT (window), _kb_mode_data_destroy, data);
}
#endif

void gldi_wayland_grab_keyboard (GldiContainer *pContainer)
{
#ifdef HAVE_GTK_LAYER_SHELL
	GtkWindow* window = GTK_WINDOW (pContainer->pWidget);
	gtk_layer_set_keyboard_mode (window, GTK_LAYER_SHELL_KEYBOARD_MODE_EXCLUSIVE);
	/* Note: the above will trigger a commit, but not immediately, while we should
	 * not commit ourselves as the associated wl_surface might be in an inconsistent state.
	 * So we set up a callback to reset to GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND,
	 * hopefully after this change has been comitted. For the complexities involved, see e.g.
	 * https://github.com/wmww/gtk-layer-shell/issues/51
	 * https://github.com/wmww/gtk-layer-shell/issues/143
	 * (this case is less severe as likely no "breaking" change is happening, but we still
	 * should not commit behind the back of GTK) */
	_set_kb_mode_callback (window);
#endif
}


static void _release_keyboard_activate (void)
{
#ifdef HAVE_WAYLAND_PROTOCOLS
	GldiWindowActor *actor = gldi_wayland_wm_get_last_active_window ();
	if (actor && !actor->bIsHidden) {
		if (gldi_window_manager_can_track_workspaces () && !gldi_window_is_on_current_desktop (actor))
			return;
		gldi_window_show (actor);
	}
#endif
}

static void _release_keyboard_layer_shell (GldiContainer *pContainer)
{
	if (!pContainer) return;
#ifdef HAVE_GTK_LAYER_SHELL
	GtkWindow* window = GTK_WINDOW (pContainer->pWidget);
	gtk_layer_set_keyboard_mode (window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE);
	// See comment in gldi_wayland_grab_keyboard () above.
	_set_kb_mode_callback (window);
#endif	
}

void gldi_wayland_release_keyboard (GldiContainer *pContainer, GldiWaylandReleaseKeyboardReason reason)
{
	if (s_CompositorType == WAYLAND_COMPOSITOR_NONE)
		return; // not a Wayland session
	
	if (s_CompositorType == WAYLAND_COMPOSITOR_UNKNOWN)
		_try_detect_compositor ();
	
	if (reason == GLDI_KEYBOARD_RELEASE_PRESENT_WINDOWS)
	{
		if (s_CompositorType != WAYLAND_COMPOSITOR_WAYFIRE && s_CompositorType != WAYLAND_COMPOSITOR_NIRI)
			return; // extra keyboard release in this case is only needed on Wayfire and niri
	}
	if (reason == GLDI_KEYBOARD_RELEASE_PRESENT_DESKTOPS && s_CompositorType != WAYLAND_COMPOSITOR_NIRI)
		return; // extra keyboard release in this case is only needed on niri
	
	switch (s_CompositorType)
	{
		// note: NONE is handled above already
		case WAYLAND_COMPOSITOR_WAYFIRE:
			if (reason == GLDI_KEYBOARD_RELEASE_PRESENT_WINDOWS)
				_release_keyboard_layer_shell (pContainer); // depends on https://github.com/WayfireWM/wayfire/pull/2530
			break; // no need to do anything when closing menus
		case WAYLAND_COMPOSITOR_KWIN:
		case WAYLAND_COMPOSITOR_COSMIC:
			_release_keyboard_activate ();
			break;
		default: // generic, niri and unknown
			_release_keyboard_layer_shell (pContainer);
			break;
	}
}

static gboolean _dock_handle_leave (CairoDock *pDock, GdkEventCrossing *pEvent)
{
	if (pEvent) pDock->iMousePositionType = CAIRO_DOCK_MOUSE_OUTSIDE;
	else
	{
		GdkSeat *pSeat = gdk_display_get_default_seat (gdk_display_get_default());
		GdkDevice *pDevice = gdk_seat_get_pointer (pSeat);
		int tmpx, tmpy;
		GdkWindow *win = gdk_device_get_window_at_position (pDevice, &tmpx, &tmpy);
		if (win != gldi_container_get_gdk_window (CAIRO_CONTAINER (pDock)))
			pDock->iMousePositionType = CAIRO_DOCK_MOUSE_OUTSIDE;
	}
	return (pDock->iMousePositionType == CAIRO_DOCK_MOUSE_OUTSIDE);
}

static void _dock_handle_enter (CairoDock *pDock, G_GNUC_UNUSED GdkEventCrossing *pEvent)
{
	pDock->iMousePositionType = CAIRO_DOCK_MOUSE_INSIDE;
}

static void _adjust_aimed_point (const Icon *pIcon, G_GNUC_UNUSED GtkWidget *pWidget, int w, int h,
	int iMarginPosition, gdouble fAlign, int *iAimedX, int *iAimedY)
{
	// gtk-layer-shell < 8.2.0: no relative position is available, we use
	// heuristics to decide if the container was slided on the screen
	gldi_container_calculate_aimed_point_base (w, h, iMarginPosition, fAlign, iAimedX, iAimedY);
	
	GldiContainer *pContainer = (pIcon ? cairo_dock_get_icon_container (pIcon) : NULL);
	if (! (pIcon && pContainer) ) return;
	if (!CAIRO_DOCK_IS_DOCK (pContainer)) return;
	
	CairoDock* pDock = (CairoDock*)pContainer;
	int W = cairo_dock_get_screen_width (pDock->iNumScreen);
	int H = cairo_dock_get_screen_height (pDock->iNumScreen);
	int dockX = 0;
	gint dockW, dockH;
	gtk_window_get_size (GTK_WINDOW (pContainer->pWidget), &dockW, &dockH);
	
	if (pContainer->bIsHorizontal) dockX = (W - dockW) / 2;
	else dockX = (H - dockH) / 2;
	if (dockX < 0) dockX = 0;
	
	// see if the new container is likely to be slided and adjust aimed points
	if (iMarginPosition == 0 || iMarginPosition == 1)
	{
		int x0 = dockX + pIcon->fDrawX + pIcon->fWidth * pIcon->fScale / 2.0;
		if (x0 < w * fAlign) *iAimedX = x0;
		else if (W - x0 < w * (1 - fAlign)) *iAimedX = w - (W - x0);
	}
	else
	{
		int y0 = dockX + pIcon->fDrawX + pIcon->fWidth * pIcon->fScale / 2.0;
		if (y0 < h * fAlign) *iAimedY = y0;
		else if (y0 > H - h * (1 - fAlign)) *iAimedY += y0 - (H - h / 2);
	}
}

static gboolean _is_wayland() { return TRUE; }


static gboolean s_bInitializing = TRUE;  // each time a callback is called on startup, it will set this to TRUE, and we'll make a roundtrip to the server until no callback is called.

static void _registry_global_cb ( G_GNUC_UNUSED void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
	cd_debug ("got a new global object, instance of %s, id=%d", interface, id);
	if (!strcmp (interface, wl_compositor_interface.name))
	{
		s_pCompositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
	}
#ifdef HAVE_WAYLAND_PROTOCOLS
	else if (gldi_wayland_hotspots_match_protocol (id, interface, version))
	{
		/* this space is intentionally left blank */
	}
	else if (gldi_wlr_foreign_toplevel_match_protocol (id, interface, version))
	{
		cd_debug("Found foreign-toplevel-manager");
	}
	else if (gldi_plasma_window_manager_match_protocol (id, interface, version))
	{
		cd_debug("Found plasma-window-manager");
	}
	else if (gldi_plasma_virtual_desktop_match_protocol (id, interface, version))
	{
		cd_debug("Found plasma-virtual-desktop-manager");
	}
	else if (gldi_cosmic_toplevel_match_protocol (id, interface, version))
	{
		cd_debug("Found cosmic-toplevel-manager");
	}
	else if (gldi_ext_workspaces_match_protocol (id, interface, version))
	{
		cd_debug("Found ext-workspace-manager");
	}
#else
	(void)version; // avoid warning
#endif
	s_bInitializing = TRUE;
}

static void _registry_global_remove_cb (G_GNUC_UNUSED void *data, G_GNUC_UNUSED struct wl_registry *registry, uint32_t id)
{
	cd_debug ("got a global object has disappeared: id=%d", id);
	/// TODO: find it and destroy it...
}

static const struct wl_registry_listener registry_listener = {
	_registry_global_cb,
	_registry_global_remove_cb
};


static void init (void)
{
	//\__________________ listen for Wayland events
	struct wl_registry *registry = wl_display_get_registry (s_pDisplay);
	wl_registry_add_listener (registry, &registry_listener, NULL);
	do
	{
		s_bInitializing = FALSE;
		wl_display_roundtrip (s_pDisplay);
	}
	while (s_bInitializing);
	
	GldiContainerManagerBackend cmb;
	memset (&cmb, 0, sizeof (GldiContainerManagerBackend));	
#ifdef HAVE_GTK_LAYER_SHELL
	s_bHave_Layer_Shell = !g_bDisableLayerShell && gtk_layer_is_supported ();
	if (s_bHave_Layer_Shell)
	{
		cmb.reserve_space = _layer_shell_reserve_space;
		cmb.init_layer = _layer_shell_init_for_window;
		cmb.set_keep_below = _set_keep_below;
		cmb.set_monitor = _layer_shell_move_to_monitor;
		guint major = gtk_layer_get_major_version ();
		if (!major)
		{
			guint minor = gtk_layer_get_minor_version ();
			guint micro = gtk_layer_get_micro_version ();
			if (minor < 8 || (minor == 8 && micro < 2))
				cmb.adjust_aimed_point = _adjust_aimed_point;
		}
	}
#endif
	GldiWaylandHotspotsType hotspots_type = gldi_wayland_hotspots_try_init (registry);

	if (hotspots_type != GLDI_WAYLAND_HOTSPOTS_NONE)
	{
		cmb.update_polling_screen_edge = gldi_wayland_hotspots_update;
		if (hotspots_type == GLDI_WAYLAND_HOTSPOTS_WAYFIRE)
			s_CompositorType = WAYLAND_COMPOSITOR_WAYFIRE;
	}
#ifdef HAVE_WAYLAND_PROTOCOLS
	gboolean bCosmic = gldi_cosmic_toplevel_try_init (registry);
	if (bCosmic)
	{
		if (s_CompositorType != WAYLAND_COMPOSITOR_UNKNOWN)
			cd_warning ("inconsistent compositor types detected!");
		s_CompositorType = WAYLAND_COMPOSITOR_COSMIC;
		if (!gldi_ext_workspaces_try_init (registry))
			gldi_plasma_virtual_desktop_try_init (registry);
	}
	else
	{
		if (gldi_plasma_window_manager_try_init (registry))
		{
			if (s_CompositorType != WAYLAND_COMPOSITOR_UNKNOWN)
				cd_warning ("inconsistent compositor types detected!");
			s_CompositorType = WAYLAND_COMPOSITOR_KWIN;
		}
		else gldi_wlr_foreign_toplevel_try_init (registry);
		if (!gldi_plasma_virtual_desktop_try_init (registry))
			gldi_ext_workspaces_try_init (registry);
	}
#endif	
	cmb.set_input_shape = _set_input_shape;
	cmb.is_wayland = _is_wayland;
	cmb.move_resize_dock = _move_resize_dock;
	cmb.dock_handle_leave = _dock_handle_leave;
	cmb.dock_handle_enter = _dock_handle_enter;
	gldi_container_manager_register_backend (&cmb);
	gldi_register_egl_backend ();
}

void gldi_register_wayland_manager (void)
{
	#ifdef GDK_WINDOWING_WAYLAND  // if GTK doesn't support Wayland, there is no point in trying
	GdkDisplay *dsp = gdk_display_get_default ();  // let's GDK do the guess
	if (GDK_IS_WAYLAND_DISPLAY (dsp))
	{
		s_pDisplay = gdk_wayland_display_get_wl_display (dsp);
	}
	if (!s_pDisplay)  // if not an Wayland session
	#endif
	{
		cd_message ("Not an Wayland session");
		return;
	}
	
	s_CompositorType = WAYLAND_COMPOSITOR_UNKNOWN;
	g_desktopGeometry.iNbDesktops = g_desktopGeometry.iNbViewportX = g_desktopGeometry.iNbViewportY = 1;
	
	// Manager
	memset (&myWaylandMgr, 0, sizeof (GldiManager));
	myWaylandMgr.cModuleName   = "Wayland";
	myWaylandMgr.init          = init;
	myWaylandMgr.load          = NULL;
	myWaylandMgr.unload        = NULL;
	myWaylandMgr.reload        = (GldiManagerReloadFunc)NULL;
	myWaylandMgr.get_config    = (GldiManagerGetConfigFunc)NULL;
	myWaylandMgr.reset_config  = (GldiManagerResetConfigFunc)NULL;
	// Config
	myWaylandMgr.pConfig = (GldiManagerConfigPtr)NULL;
	myWaylandMgr.iSizeOfConfig = 0;
	// data
	myWaylandMgr.iSizeOfData = 0;
	myWaylandMgr.pData = (GldiManagerDataPtr)NULL;
	gldi_object_install_notifications (&myWaylandMgr, NB_NOTIFICATIONS_WAYLAND_DESKTOP);
	// register
	gldi_object_init (GLDI_OBJECT(&myWaylandMgr), &myManagerObjectMgr, NULL);
}

gboolean gldi_wayland_manager_have_layer_shell ()
{
	return s_bHave_Layer_Shell;
}

const gchar *gldi_wayland_get_detected_compositor (void)
{
	switch (s_CompositorType)
	{
		case WAYLAND_COMPOSITOR_NONE:
			return "none";
		case WAYLAND_COMPOSITOR_KWIN:
			return "KWin";
		case WAYLAND_COMPOSITOR_WAYFIRE:
			return "Wayfire";
		case WAYLAND_COMPOSITOR_COSMIC:
			return "Cosmic";
		case WAYLAND_COMPOSITOR_GENERIC:
			return "generic";
		case WAYLAND_COMPOSITOR_NIRI:
			return "niri";
		case WAYLAND_COMPOSITOR_UNKNOWN:
			// try to detect based on the desktop manager backend
			_try_detect_compositor ();
			if (s_CompositorType != WAYLAND_COMPOSITOR_UNKNOWN)
				return gldi_wayland_get_detected_compositor ();
			break;
	}
	return "unknown";
}

#else // HAVE_WAYLAND

#include "cairo-dock-log.h"
#include "cairo-dock-container-priv.h"
#define _MANAGER_DEF_
#include "cairo-dock-wayland-manager.h"

gboolean g_bDisableLayerShell = FALSE;
void gldi_register_wayland_manager (void)
{
	cd_message ("Cairo-Dock was not built with Wayland support");
}
gboolean gldi_wayland_manager_have_layer_shell ()
{
	return FALSE;
}

void gldi_wayland_grab_keyboard (GldiContainer*) { }
void gldi_wayland_release_keyboard (GldiContainer*, GldiWaylandReleaseKeyboardReason) { }

const gchar *gldi_wayland_get_detected_compositor (void)
{
	return "none";
}

#endif
