/*
 * Copyright (C) 2018-2023 Purism SPC
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * Author: Guido Günther <agx@sigxcpu.org>
 */
/*

WARNING: this file is taken directly from phosh, with no modificaions apart 
from this message. Please update phosh instead of changing this file. Please 
copy the file back here afterwards, with an updated version of this notice.

Up-to-date with Phosh revision 953894a2.

*/

#define G_LOG_DOMAIN "phosh-layer-surface"

#include "config.h"
#include "layersurface.h"

#include <gdk/gdkwayland.h>

/**
 * PhoshLayerSurface:
 *
 * A #GtkWindow rendered as a LayerSurface by the compositor
 *
 * #PhoshLayerSurface allows to use a Wayland surface backed by the
 * layer-shell protocol as #GtkWindow. This allows to render e.g. panels and
 * backgrounds using GTK.
 */

enum {
  PHOSH_LAYER_SURFACE_PROP_0,
  PHOSH_LAYER_SURFACE_PROP_LAYER_SHELL,
  PHOSH_LAYER_SURFACE_PROP_WL_OUTPUT,
  PHOSH_LAYER_SURFACE_PROP_ANCHOR,
  PHOSH_LAYER_SURFACE_PROP_LAYER,
  PHOSH_LAYER_SURFACE_PROP_KBD_INTERACTIVITY,
  PHOSH_LAYER_SURFACE_PROP_EXCLUSIVE_ZONE,
  PHOSH_LAYER_SURFACE_PROP_MARGIN_TOP,
  PHOSH_LAYER_SURFACE_PROP_MARGIN_BOTTOM,
  PHOSH_LAYER_SURFACE_PROP_MARGIN_LEFT,
  PHOSH_LAYER_SURFACE_PROP_MARGIN_RIGHT,
  PHOSH_LAYER_SURFACE_PROP_LAYER_WIDTH,
  PHOSH_LAYER_SURFACE_PROP_LAYER_HEIGHT,
  PHOSH_LAYER_SURFACE_PROP_CONFIGURED_WIDTH,
  PHOSH_LAYER_SURFACE_PROP_CONFIGURED_HEIGHT,
  PHOSH_LAYER_SURFACE_PROP_NAMESPACE,
  PHOSH_LAYER_SURFACE_PROP_LAST_PROP
};
static GParamSpec *props[PHOSH_LAYER_SURFACE_PROP_LAST_PROP];

enum {
  CONFIGURED,
  N_SIGNALS
};
static guint signals [N_SIGNALS];

typedef struct {
  struct wl_surface            *wl_surface;
  struct zwlr_layer_surface_v1 *layer_surface;

  /* Properties */
  guint                         anchor;
  guint                         layer;
  gboolean                      kbd_interactivity;
  int                           exclusive_zone;
  int                           margin_top, margin_bottom;
  int                           margin_left, margin_right;
  int                           width, height;
  int                           configured_width, configured_height;
  char                         *namespace;
  struct zwlr_layer_shell_v1   *layer_shell;
  struct wl_output             *wl_output;
} PhoshLayerSurfacePrivate;

G_DEFINE_TYPE_WITH_PRIVATE (PhoshLayerSurface, phosh_layer_surface, GTK_TYPE_WINDOW)


static void
layer_surface_configure (void                         *data,
                         struct zwlr_layer_surface_v1 *surface,
                         uint32_t                      serial,
                         uint32_t                      width,
                         uint32_t                      height)
{
  PhoshLayerSurface *self = data;
  PhoshLayerSurfacePrivate *priv;
  gboolean changed = FALSE;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);
  gtk_window_resize (GTK_WINDOW (self), width, height);
  zwlr_layer_surface_v1_ack_configure (surface, serial);

  if (priv->configured_height != height) {
    priv->configured_height = height;
    changed = TRUE;
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_CONFIGURED_HEIGHT]);
  }

  if (priv->configured_width != width) {
    priv->configured_width = width;
    changed = TRUE;
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_CONFIGURED_WIDTH]);
  }

  g_debug ("Configured %s (%p) (%dx%d)", priv->namespace, self, width, height);
  if (changed) {
    g_signal_emit (self, signals[CONFIGURED], 0);
  }
}


static void
layer_surface_closed (void                         *data,
                      struct zwlr_layer_surface_v1 *surface)
{
  PhoshLayerSurface *self = data;
  PhoshLayerSurfacePrivate *priv = phosh_layer_surface_get_instance_private (self);

  g_return_if_fail (priv->layer_surface == surface);
  g_debug ("Destroying layer surface '%s'", priv->namespace);
  zwlr_layer_surface_v1_destroy (priv->layer_surface);
  priv->layer_surface = NULL;
  gtk_widget_destroy (GTK_WIDGET (self));
}


static struct zwlr_layer_surface_v1_listener layer_surface_listener = {
  .configure = layer_surface_configure,
  .closed = layer_surface_closed,
};


static void
phosh_layer_surface_set_property (GObject      *object,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (object);
  PhoshLayerSurfacePrivate *priv = phosh_layer_surface_get_instance_private (self);
  int width, height;

  switch (property_id) {
  case PHOSH_LAYER_SURFACE_PROP_LAYER_SHELL:
    priv->layer_shell = g_value_get_pointer (value);
    break;
  case PHOSH_LAYER_SURFACE_PROP_WL_OUTPUT:
    priv->wl_output = g_value_get_pointer (value);
    break;
  case PHOSH_LAYER_SURFACE_PROP_ANCHOR:
    priv->anchor = g_value_get_uint (value);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER:
    phosh_layer_surface_set_layer (self, g_value_get_uint (value));
    break;
  case PHOSH_LAYER_SURFACE_PROP_KBD_INTERACTIVITY:
    phosh_layer_surface_set_kbd_interactivity (self, g_value_get_boolean (value));
    break;
  case PHOSH_LAYER_SURFACE_PROP_EXCLUSIVE_ZONE:
    phosh_layer_surface_set_exclusive_zone (self, g_value_get_int (value));
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_TOP:
    phosh_layer_surface_set_margins (self,
                                     g_value_get_int (value),
                                     priv->margin_right,
                                     priv->margin_bottom,
                                     priv->margin_left);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_BOTTOM:
    phosh_layer_surface_set_margins (self,
                                     priv->margin_top,
                                     priv->margin_right,
                                     g_value_get_int (value),
                                     priv->margin_left);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_LEFT:
    phosh_layer_surface_set_margins (self,
                                     priv->margin_top,
                                     priv->margin_right,
                                     priv->margin_bottom,
                                     g_value_get_int (value));
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_RIGHT:
    phosh_layer_surface_set_margins (self,
                                     priv->margin_top,
                                     g_value_get_int (value),
                                     priv->margin_bottom,
                                     priv->margin_left);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER_WIDTH:
    width = g_value_get_uint (value);
    phosh_layer_surface_set_size (self, width, priv->height);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER_HEIGHT:
    height = g_value_get_uint (value);
    phosh_layer_surface_set_size (self, priv->width, height);
    break;
  case PHOSH_LAYER_SURFACE_PROP_NAMESPACE:
    g_free (priv->namespace);
    priv->namespace = g_value_dup_string (value);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}


static void
phosh_layer_surface_get_property (GObject    *object,
                                  guint       property_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (object);
  PhoshLayerSurfacePrivate *priv = phosh_layer_surface_get_instance_private (self);

  switch (property_id) {
  case PHOSH_LAYER_SURFACE_PROP_LAYER_SHELL:
    g_value_set_pointer (value, priv->layer_shell);
    break;
  case PHOSH_LAYER_SURFACE_PROP_WL_OUTPUT:
    g_value_set_pointer (value, priv->wl_output);
    break;
  case PHOSH_LAYER_SURFACE_PROP_ANCHOR:
    g_value_set_uint (value, priv->anchor);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER:
    g_value_set_uint (value, priv->layer);
    break;
  case PHOSH_LAYER_SURFACE_PROP_KBD_INTERACTIVITY:
    g_value_set_boolean (value, priv->kbd_interactivity);
    break;
  case PHOSH_LAYER_SURFACE_PROP_EXCLUSIVE_ZONE:
    g_value_set_int (value, priv->exclusive_zone);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_TOP:
    g_value_set_int (value, priv->margin_top);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_BOTTOM:
    g_value_set_int (value, priv->margin_bottom);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_LEFT:
    g_value_set_int (value, priv->margin_left);
    break;
  case PHOSH_LAYER_SURFACE_PROP_MARGIN_RIGHT:
    g_value_set_int (value, priv->margin_right);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER_WIDTH:
    g_value_set_uint (value, priv->width);
    break;
  case PHOSH_LAYER_SURFACE_PROP_LAYER_HEIGHT:
    g_value_set_uint (value, priv->height);
    break;
  case PHOSH_LAYER_SURFACE_PROP_CONFIGURED_WIDTH:
    g_value_set_uint (value, priv->configured_width);
    break;
  case PHOSH_LAYER_SURFACE_PROP_CONFIGURED_HEIGHT:
    g_value_set_uint (value, priv->configured_height);
    break;
  case PHOSH_LAYER_SURFACE_PROP_NAMESPACE:
    g_value_set_string (value, priv->namespace);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}


static void
phosh_layer_surface_realize (GtkWidget *widget)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (widget);
  PhoshLayerSurfacePrivate *priv;
  GdkWindow *gdk_window;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  GTK_WIDGET_CLASS (phosh_layer_surface_parent_class)->realize (widget);

  gdk_window = gtk_widget_get_window (GTK_WIDGET (self));
  gdk_wayland_window_set_use_custom_surface (gdk_window);
  priv->wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);

  gtk_window_set_decorated (GTK_WINDOW (self), FALSE);
}


static void
phosh_layer_surface_map (GtkWidget *widget)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (widget);
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  GTK_WIDGET_CLASS (phosh_layer_surface_parent_class)->map (widget);

  if (!priv->wl_surface) {
    GdkWindow *gdk_window;

    gdk_window = gtk_widget_get_window (GTK_WIDGET (self));
    gdk_wayland_window_set_use_custom_surface (gdk_window);
    priv->wl_surface = gdk_wayland_window_get_wl_surface (gdk_window);
  }
  g_debug ("Mapped %p, namespace: %s", priv->wl_surface, priv->namespace);

  priv->layer_surface = zwlr_layer_shell_v1_get_layer_surface (priv->layer_shell,
                                                               priv->wl_surface,
                                                               priv->wl_output,
                                                               priv->layer,
                                                               priv->namespace);
  zwlr_layer_surface_v1_set_exclusive_zone (priv->layer_surface, priv->exclusive_zone);
  zwlr_layer_surface_v1_set_size (priv->layer_surface, priv->width, priv->height);
  zwlr_layer_surface_v1_set_anchor (priv->layer_surface, priv->anchor);
  zwlr_layer_surface_v1_set_margin (priv->layer_surface,
                                    priv->margin_top,
                                    priv->margin_right,
                                    priv->margin_bottom,
                                    priv->margin_left);
  zwlr_layer_surface_v1_set_keyboard_interactivity (priv->layer_surface, priv->kbd_interactivity);
  zwlr_layer_surface_v1_add_listener (priv->layer_surface,
                                      &layer_surface_listener,
                                      self);
  wl_surface_commit (priv->wl_surface);

  /* Process all pending events, otherwise we end up sending ack configure
   * to a not yet configured surface */
  wl_display_roundtrip (gdk_wayland_display_get_wl_display (gdk_display_get_default ()));
}


static void
phosh_layer_surface_unmap (GtkWidget *widget)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (widget);
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  g_clear_pointer (&priv->layer_surface, zwlr_layer_surface_v1_destroy);
  priv->wl_surface = NULL;

  GTK_WIDGET_CLASS (phosh_layer_surface_parent_class)->unmap (widget);
}


static void
phosh_layer_surface_dispose (GObject *object)
{
  PhoshLayerSurface *self = PHOSH_LAYER_SURFACE (object);
  PhoshLayerSurfacePrivate *priv = phosh_layer_surface_get_instance_private (self);

  g_clear_pointer (&priv->layer_surface, zwlr_layer_surface_v1_destroy);
  g_clear_pointer (&priv->namespace, g_free);

  G_OBJECT_CLASS (phosh_layer_surface_parent_class)->dispose (object);
}


static void
phosh_layer_surface_class_init (PhoshLayerSurfaceClass *klass)
{
  GObjectClass *object_class = (GObjectClass *)klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;

  object_class->dispose = phosh_layer_surface_dispose;
  object_class->set_property = phosh_layer_surface_set_property;
  object_class->get_property = phosh_layer_surface_get_property;

  widget_class->realize = phosh_layer_surface_realize;
  widget_class->map = phosh_layer_surface_map;
  widget_class->unmap = phosh_layer_surface_unmap;

  props[PHOSH_LAYER_SURFACE_PROP_LAYER_SHELL] =
    g_param_spec_pointer (
      "layer-shell",
      "Wayland Layer Shell Global",
      "The layer shell wayland global",
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  props[PHOSH_LAYER_SURFACE_PROP_WL_OUTPUT] =
    g_param_spec_pointer (
      "wl-output",
      "Wayland Output",
      "The wl_output associated with this surface",
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  props[PHOSH_LAYER_SURFACE_PROP_ANCHOR] =
    g_param_spec_uint (
      "anchor",
      "Anchor edges",
      "The edges to anchor the surface to",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  props[PHOSH_LAYER_SURFACE_PROP_LAYER] =
    g_param_spec_uint (
      "layer",
      "Layer",
      "The layer the surface should be attached to",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_KBD_INTERACTIVITY] =
    g_param_spec_boolean (
      "kbd-interactivity",
      "Keyboard interactivity",
      "Whether the surface interacts with the keyboard",
      FALSE,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_EXCLUSIVE_ZONE] =
    g_param_spec_int (
      "exclusive-zone",
      "Exclusive Zone",
      "Set area that is not occluded with other surfaces",
      -1,
      G_MAXINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_MARGIN_LEFT] =
    g_param_spec_int (
      "margin-left",
      "Left margin",
      "Distance away from the left anchor point",
      G_MININT,
      G_MAXINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_MARGIN_RIGHT] =
    g_param_spec_int (
      "margin-right",
      "Right margin",
      "Distance away from the right anchor point",
      G_MININT,
      G_MAXINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_MARGIN_TOP] =
    g_param_spec_int (
      "margin-top",
      "Top margin",
      "Distance away from the top anchor point",
      G_MININT,
      G_MAXINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_MARGIN_BOTTOM] =
    g_param_spec_int (
      "margin-bottom",
      "Bottom margin",
      "Distance away from the bottom anchor point",
      G_MININT,
      G_MAXINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_LAYER_WIDTH] =
    g_param_spec_uint (
      "width",
      "Width",
      "The width of the layer surface",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_LAYER_HEIGHT] =
    g_param_spec_uint (
      "height",
      "Height",
      "The height of the layer surface",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);


  props[PHOSH_LAYER_SURFACE_PROP_CONFIGURED_WIDTH] =
    g_param_spec_uint (
      "configured-width",
      "Configured width",
      "The width of the layer surface set by the compositor",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_CONFIGURED_HEIGHT] =
    g_param_spec_uint (
      "configured-height",
      "Configured height",
      "The height of the layer surface set by the compositor",
      0,
      G_MAXUINT,
      0,
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  props[PHOSH_LAYER_SURFACE_PROP_NAMESPACE] =
    g_param_spec_string (
      "namespace",
      "Namespace",
      "Namespace of the layer surface",
      "",
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, PHOSH_LAYER_SURFACE_PROP_LAST_PROP, props);

  /**
   * PhoshLayerSurface::configured
   * @self: The #PhoshLayerSurface instance.
   *
   * This signal is emitted once we received the configure event from the
   * compositor.
   */
  signals[CONFIGURED] =
    g_signal_new ("configured",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PhoshLayerSurfaceClass, configured),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);
}


static void
phosh_layer_surface_init (PhoshLayerSurface *self)
{
}


GtkWidget *
phosh_layer_surface_new (gpointer layer_shell,
                         gpointer wl_output)
{
  return g_object_new (PHOSH_TYPE_LAYER_SURFACE,
                       "layer-shell", layer_shell,
                       "wl-output", wl_output,
                       NULL);
}


/**
 * phosh_layer_surface_get_surface:
 * @self: The #PhoshLayerSurface
 *
 * Get the layer layer surface or #NULL if the window
 * is not yet realized.
 */
struct zwlr_layer_surface_v1 *
phosh_layer_surface_get_layer_surface (PhoshLayerSurface *self)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_val_if_fail (PHOSH_IS_LAYER_SURFACE (self), NULL);
  priv = phosh_layer_surface_get_instance_private (self);
  return priv->layer_surface;
}


/**
 * phosh_layer_surface_get_wl_surface:
 * @self: The #PhoshLayerSurface
 *
 * Get the layer wayland surface or #NULL if the window
 * is not yet realized.
 */
struct wl_surface *
phosh_layer_surface_get_wl_surface (PhoshLayerSurface *self)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_val_if_fail (PHOSH_IS_LAYER_SURFACE (self), NULL);
  priv = phosh_layer_surface_get_instance_private (self);
  return priv->wl_surface;
}


/**
 * phosh_layer_surface_set_size:
 * @self: The #PhoshLayerSurface
 * @width: the height in pixels
 * @height: the width in pixels
 *
 * Set the size of a layer surface. A value of '-1' indicates 'use old value'
 */
void
phosh_layer_surface_set_size (PhoshLayerSurface *self, int width, int height)
{
  PhoshLayerSurfacePrivate *priv;
  int old_width, old_height;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  if (priv->height == height && priv->width == width) {
    return;
  }

  old_width = priv->width;
  old_height = priv->height;

  if (width != -1) {
    priv->width = width;
  }

  if (height != -1) {
    priv->height = height;
  }

  if (gtk_widget_get_mapped (GTK_WIDGET (self))) {
    zwlr_layer_surface_v1_set_size (priv->layer_surface, priv->width, priv->height);
  }

  if (priv->height != old_height) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_LAYER_HEIGHT]);
  }

  if (priv->width != old_width) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_LAYER_WIDTH]);
  }
}


/**
 * phosh_layer_surface_set_margins:
 * @self: The #PhoshLayerSurface
 * @top: the top margin in pixels
 * @right: the right margin in pixels
 * @bottom: the bottom margin in pixels
 * @left: the left margin in pixels
 *
 * Set anchor margins of a layer surface.
 */
void
phosh_layer_surface_set_margins (PhoshLayerSurface *self, int top, int right, int bottom, int left)
{
  PhoshLayerSurfacePrivate *priv;
  int old_top, old_bottom, old_left, old_right;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  old_top = priv->margin_top;
  old_left = priv->margin_left;
  old_right = priv->margin_right;
  old_bottom = priv->margin_bottom;

  if (old_top == top && old_left == left && old_right == right && old_bottom == bottom) {
    return;
  }

  priv->margin_top = top;
  priv->margin_left = left;
  priv->margin_right = right;
  priv->margin_bottom = bottom;

  if (priv->layer_surface) {
    zwlr_layer_surface_v1_set_margin (priv->layer_surface, top, right, bottom, left);
  }

  if (old_top != top) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_MARGIN_TOP]);
  }
  if (old_bottom != bottom) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_MARGIN_BOTTOM]);
  }
  if (old_left != left) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_MARGIN_LEFT]);
  }
  if (old_right != right) {
    g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_MARGIN_RIGHT]);
  }
}


/**
 * phosh_layer_surface_set_exclusive_zone:
 * @self: The #PhoshLayerSurface
 * @zone: Size of the exclusive zone.
 *
 * Set exclusive zone of a layer surface.
 */
void
phosh_layer_surface_set_exclusive_zone (PhoshLayerSurface *self, int zone)
{
  PhoshLayerSurfacePrivate *priv;
  int old_zone;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  old_zone = priv->exclusive_zone;

  if (old_zone == zone) {
    return;
  }

  priv->exclusive_zone = zone;

  if (priv->layer_surface) {
    zwlr_layer_surface_v1_set_exclusive_zone (priv->layer_surface, zone);
  }

  g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_EXCLUSIVE_ZONE]);
}


/**
 * phosh_layer_surface_set_keyboard_interactivity:
 * @self: The #PhoshLayerSurface
 * @interactivity: %TRUE if the #PhoshLayerSurface should receive keyboard input.
 *
 * Set keyboard ineractivity a layer surface.
 */
void
phosh_layer_surface_set_kbd_interactivity (PhoshLayerSurface *self, gboolean interactivity)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  if (priv->kbd_interactivity == interactivity) {
    return;
  }

  priv->kbd_interactivity = interactivity;

  if (priv->layer_surface) {
    zwlr_layer_surface_v1_set_keyboard_interactivity (priv->layer_surface, interactivity);
  }

  g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_KBD_INTERACTIVITY]);
}


/**
 * phosh_layer_surface_set_layer:
 * @self: The #PhoshLayerSurface
 * @layer: The layer.
 *
 * Sets the layer a layer-surface belongs to `layer`.
 */
void
phosh_layer_surface_set_layer (PhoshLayerSurface *self, guint32 layer)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  if (priv->layer == layer) {
    return;
  }

  priv->layer = layer;

  if (priv->layer_surface) {
    zwlr_layer_surface_v1_set_layer (priv->layer_surface, layer);
  }

  g_object_notify_by_pspec (G_OBJECT (self), props[PHOSH_LAYER_SURFACE_PROP_LAYER]);
}


/**
 * phosh_layer_surface_wl_surface_commit:
 * @self: The #PhoshLayerSurface
 *
 * Forces a commit of layer surface's state.
 */
void
phosh_layer_surface_wl_surface_commit (PhoshLayerSurface *self)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  if (priv->wl_surface) {
    wl_surface_commit (priv->wl_surface);
  }
}


void
phosh_layer_surface_get_margins (PhoshLayerSurface *self, int *top, int *right, int *bottom, int *left)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
  priv = phosh_layer_surface_get_instance_private (self);

  if (top) {
    *top = priv->margin_top;
  }

  if (right) {
    *right = priv->margin_right;
  }

  if (bottom) {
    *bottom = priv->margin_bottom;
  }

  if (left) {
    *left = priv->margin_left;
  }
}


int
phosh_layer_surface_get_configured_width (PhoshLayerSurface *self)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_val_if_fail (PHOSH_IS_LAYER_SURFACE (self), 0);
  priv = phosh_layer_surface_get_instance_private (self);

  return priv->configured_width;
}


int
phosh_layer_surface_get_configured_height (PhoshLayerSurface *self)
{
  PhoshLayerSurfacePrivate *priv;

  g_return_val_if_fail (PHOSH_IS_LAYER_SURFACE (self), 0);
  priv = phosh_layer_surface_get_instance_private (self);

  return priv->configured_height;
}
