/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/**
 * SECTION:window-props
 * @short_description: #MetaWindow property handling
 *
 * A system which can inspect sets of properties of given windows
 * and take appropriate action given their values.
 *
 * Note that all the meta_window_reload_propert* functions require a
 * round trip to the server.
 *
 * The guts of this system are in meta_display_init_window_prop_hooks().
 * Reading this function will give you insight into how this all fits
 * together.
 */

/* 
 * Copyright (C) 2001, 2002, 2003 Red Hat, Inc.
 * Copyright (C) 2004, 2005 Elijah Newren
 * Copyright (C) 2009 Thomas Thurman
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
 * 02110-1335, USA.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#define _SVID_SOURCE /* for gethostname() */

#include <config.h>
#include "window-props.h"
#include <meta/errors.h>
#include "xprops.h"
#include "frame.h"
#include <meta/group.h>
#include <X11/Xatom.h>
#include <unistd.h>
#include <string.h>
#ifndef HOST_NAME_MAX
/* Solaris headers apparently don't define this so do so manually; #326745 */
#define HOST_NAME_MAX 255
#endif

typedef void (* ReloadValueFunc) (MetaWindow    *window,
                                  MetaPropValue *value,
                                  gboolean       initial);

struct _MetaWindowPropHooks
{
  Atom property;
  MetaPropValueType type;
  ReloadValueFunc reload_func;
  gboolean load_initially;
  gboolean include_override_redirect;
};

static void init_prop_value            (MetaWindow          *window,
                                        MetaWindowPropHooks *hooks,
                                        MetaPropValue       *value);
static void reload_prop_value          (MetaWindow          *window,
                                        MetaWindowPropHooks *hooks,
                                        MetaPropValue       *value,
                                        gboolean             initial);
static MetaWindowPropHooks* find_hooks (MetaDisplay *display,
                                        Atom         property);


LOCAL_SYMBOL void
meta_window_reload_property (MetaWindow *window,
                             Atom        property,
                             gboolean    initial)
{
  meta_window_reload_properties (window, &property, 1, initial);
}

LOCAL_SYMBOL void
meta_window_reload_properties (MetaWindow *window,
                               const Atom *properties,
                               int         n_properties,
                               gboolean    initial)
{
  meta_window_reload_properties_from_xwindow (window,
                                              window->xwindow,
                                              properties,
                                              n_properties,
                                              initial);
}

LOCAL_SYMBOL void
meta_window_reload_property_from_xwindow (MetaWindow *window,
                                          Window      xwindow,
                                          Atom        property,
                                          gboolean    initial)
{
  meta_window_reload_properties_from_xwindow (window, xwindow, &property, 1,
                                              initial);
}

LOCAL_SYMBOL void
meta_window_reload_properties_from_xwindow (MetaWindow *window,
                                            Window      xwindow,
                                            const Atom *properties,
                                            int         n_properties,
                                            gboolean    initial)
{
  int i;
  MetaPropValue *values;

  g_return_if_fail (properties != NULL);
  g_return_if_fail (n_properties > 0);
  
  values = g_new0 (MetaPropValue, n_properties);
  
  i = 0;
  while (i < n_properties)
    {
      MetaWindowPropHooks *hooks = find_hooks (window->display, properties[i]);
      init_prop_value (window, hooks, &values[i]);
      ++i;
    }
  
  meta_prop_get_values (window->display, xwindow,
                        values, n_properties);

  i = 0;
  while (i < n_properties)
    {
      MetaWindowPropHooks *hooks = find_hooks (window->display, properties[i]);
      reload_prop_value (window, hooks, &values[i], initial);
      
      ++i;
    }

  meta_prop_free_values (values, n_properties);
  
  g_free (values);
}

LOCAL_SYMBOL void
meta_window_load_initial_properties (MetaWindow *window)
{
  int i, j;
  MetaPropValue *values;
  int n_properties = 0;

  values = g_new0 (MetaPropValue, window->display->n_prop_hooks);

  j = 0;
  for (i = 0; i < window->display->n_prop_hooks; i++)
    {
      MetaWindowPropHooks *hooks = &window->display->prop_hooks_table[i];
      if (hooks->load_initially)
        {
          init_prop_value (window, hooks, &values[j]);
          ++j;
        }
    }
  n_properties = j;

  meta_prop_get_values (window->display, window->xwindow,
                        values, n_properties);

  j = 0;
  for (i = 0; i < window->display->n_prop_hooks; i++)
    {
      MetaWindowPropHooks *hooks = &window->display->prop_hooks_table[i];
      if (hooks->load_initially)
        {
          /* If we didn't actually manage to load anything then we don't need
           * to call the reload function; this is different from a notification
           * where disappearance of a previously present value is significant.
           */
          if (values[j].type != META_PROP_VALUE_INVALID)
            reload_prop_value (window, hooks, &values[j], TRUE);
          ++j;
        }
    }

  meta_prop_free_values (values, n_properties);

  g_free (values);
}

/* Fill in the MetaPropValue used to get the value of "property" */
static void
init_prop_value (MetaWindow          *window,
                 MetaWindowPropHooks *hooks,
                 MetaPropValue       *value)
{
  if (!hooks || hooks->type == META_PROP_VALUE_INVALID ||
      (window->override_redirect && !hooks->include_override_redirect))
    {
      value->type = META_PROP_VALUE_INVALID;
      value->atom = None;
    }
  else
    {
      value->type = hooks->type;
      value->atom = hooks->property;
    }
}

static void
reload_prop_value (MetaWindow          *window,
                   MetaWindowPropHooks *hooks,
                   MetaPropValue       *value,
                   gboolean             initial)
{
  if (hooks && hooks->reload_func != NULL &&
      !(window->override_redirect && !hooks->include_override_redirect))
    (* hooks->reload_func) (window, value, initial);
}

static void
reload_wm_client_machine (MetaWindow    *window,
                          MetaPropValue *value,
                          gboolean       initial)
{
  g_free (window->wm_client_machine);
  window->wm_client_machine = NULL;
  
  if (value->type != META_PROP_VALUE_INVALID)
    window->wm_client_machine = g_strdup (value->v.str);

  meta_verbose ("Window has client machine \"%s\"\n",
                window->wm_client_machine ? window->wm_client_machine : "unset");
}

static void
reload_theme_icon_name (MetaWindow    *window,
                        MetaPropValue *value,
                        gboolean       initial)
{
  g_free (window->theme_icon_name);
  window->theme_icon_name = NULL;

  if (value->type != META_PROP_VALUE_INVALID)
    window->theme_icon_name = g_strdup (value->v.str);

  meta_verbose ("Window theme icon name or path is \"%s\"\n",
                window->theme_icon_name ? window->theme_icon_name : "unset");
}

static void
reload_progress (MetaWindow    *window,
                 MetaPropValue *value,
                 gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      if (window->progress != value->v.cardinal)
        {
          window->progress = value->v.cardinal;
          g_object_notify (window, "progress");

          meta_topic (META_DEBUG_WINDOW_STATE,
                      "Read XAppGtkWindow progress prop %u for %s\n",
                      window->progress, window->desc);
        }
    }
  else
    {
      if (window->progress != 0)
        {
          window->progress = 0;
          g_object_notify (window, "progress");

          meta_topic (META_DEBUG_WINDOW_STATE,
                      "Read XAppGtkWindow progress prop %u for %s\n",
                      window->progress, window->desc);
        }
    }
}

static void
reload_progress_pulse (MetaWindow    *window,
                       MetaPropValue *value,
                       gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      gboolean new_val = value->v.cardinal == 1;

      if (window->progress_pulse != new_val)
        {
          window->progress_pulse = new_val;
          g_object_notify (window, "progress-pulse");

          meta_topic (META_DEBUG_WINDOW_STATE,
                      "Read XAppGtkWindow progress-pulse prop %s for %s\n",
                      window->progress_pulse ? "TRUE" : "FALSE", window->desc);
        }
    }
  else
    {
      if (window->progress_pulse)
        {
          window->progress_pulse = FALSE;
          g_object_notify (window, "progress-pulse");

          meta_topic (META_DEBUG_WINDOW_STATE,
                      "Read XAppGtkWindow progress-pulse prop %s for %s\n",
                      window->progress_pulse ? "TRUE" : "FALSE", window->desc);
        }
    }
}

static void
complain_about_broken_client (MetaWindow    *window,
                              MetaPropValue *value,
                              gboolean       initial)
{
  meta_warning ("Broken client! Window %s changed client leader window or SM client ID\n",
                window->desc);
}

static void
reload_net_wm_window_type (MetaWindow    *window,
                           MetaPropValue *value,
                           gboolean       initial)
{
  meta_window_update_net_wm_type (window);
}

static void
reload_icon (MetaWindow    *window,
             Atom           atom)
{
  meta_icon_cache_property_changed (&window->icon_cache,
                                    window->display,
                                    atom);
  meta_window_queue(window, META_QUEUE_UPDATE_ICON);
}

static void
reload_net_wm_icon (MetaWindow    *window,
                    MetaPropValue *value,
                    gboolean       initial)
{
  reload_icon (window, window->display->atom__NET_WM_ICON);
}

static void
reload_kwm_win_icon (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  reload_icon (window, window->display->atom__KWM_WIN_ICON);
}

static void
reload_gtk_frame_extents (MetaWindow    *window,
                          MetaPropValue *value,
                          gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      if (value->v.cardinal_list.n_cardinals != 4)
        {
          meta_verbose ("_GTK_FRAME_EXTENTS on %s has %d values instead of 4\n",
                        window->desc, value->v.cardinal_list.n_cardinals);
        }
      else
        {
          GtkBorder *extents = &window->custom_frame_extents;

          window->has_custom_frame_extents = TRUE;
          extents->left   = (int)value->v.cardinal_list.cardinals[0];
          extents->right  = (int)value->v.cardinal_list.cardinals[1];
          extents->top    = (int)value->v.cardinal_list.cardinals[2];
          extents->bottom = (int)value->v.cardinal_list.cardinals[3];
        }
    }
  else
    {
      window->has_custom_frame_extents = FALSE;
    }
}

static void
reload_icon_geometry (MetaWindow    *window,
                      MetaPropValue *value,
                      gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      if (value->v.cardinal_list.n_cardinals != 4)
        {
          meta_verbose ("_NET_WM_ICON_GEOMETRY on %s has %d values instead of 4\n",
                        window->desc, value->v.cardinal_list.n_cardinals);
        }
      else
        {
          MetaRectangle geometry;

          geometry.x = (int)value->v.cardinal_list.cardinals[0];
          geometry.y = (int)value->v.cardinal_list.cardinals[1];
          geometry.width = (int)value->v.cardinal_list.cardinals[2];
          geometry.height = (int)value->v.cardinal_list.cardinals[3];

          meta_window_set_icon_geometry (window, &geometry);
        }
    }
  else
    {
      meta_window_set_icon_geometry (window, NULL);
    }
}

static void
reload_struts (MetaWindow    *window,
               MetaPropValue *value,
               gboolean       initial)
{
  meta_window_update_struts (window);
}

static void
reload_wm_window_role (MetaWindow    *window,
                       MetaPropValue *value,
                       gboolean       initial)
{
  meta_window_update_role (window);
}

static void
reload_net_wm_pid (MetaWindow    *window,
                   MetaPropValue *value,
                   gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      gulong cardinal = (int) value->v.cardinal;
      
      if (cardinal <= 0)
        meta_warning (_("Application set a bogus _NET_WM_PID %lu\n"),
                      cardinal);
      else
        {
          window->net_wm_pid = cardinal;
          meta_verbose ("Window has _NET_WM_PID %d\n",
                        window->net_wm_pid);
        }
    }
}

static void
reload_net_wm_user_time (MetaWindow    *window,
                         MetaPropValue *value,
                         gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      gulong cardinal = value->v.cardinal;
      meta_window_set_user_time (window, cardinal);
    }
}

static void
reload_net_wm_user_time_window (MetaWindow    *window,
                                MetaPropValue *value,
                                gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      /* Unregister old NET_WM_USER_TIME_WINDOW */
      if (window->user_time_window != None)
        {
          /* See the comment to the meta_display_register_x_window call below. */
          meta_display_unregister_x_window (window->display,
                                            window->user_time_window);
          /* Don't get events on not-managed windows */
          XSelectInput (window->display->xdisplay,
                        window->user_time_window,
                        NoEventMask);
        }


      /* Obtain the new NET_WM_USER_TIME_WINDOW and register it */
      window->user_time_window = value->v.xwindow;
      if (window->user_time_window != None)
        {
          /* Kind of a hack; display.c:event_callback() ignores events
           * for unknown windows.  We make window->user_time_window
           * known by registering it with window (despite the fact
           * that window->xwindow is already registered with window).
           * This basically means that property notifies to either the
           * window->user_time_window or window->xwindow will be
           * treated identically and will result in functions for
           * window being called to update it.  Maybe we should ignore
           * any property notifies to window->user_time_window other
           * than atom__NET_WM_USER_TIME ones, but I just don't care
           * and it's not specified in the spec anyway.
           */
          meta_display_register_x_window (window->display,
                                          &window->user_time_window,
                                          window);
          /* Just listen for property notify events */
          XSelectInput (window->display->xdisplay,
                        window->user_time_window,
                        PropertyChangeMask);

          /* Manually load the _NET_WM_USER_TIME field from the given window
           * at this time as well.  If the user_time_window ever broadens in
           * scope, we'll probably want to load all relevant properties here.
           */
          meta_window_reload_property_from_xwindow (
            window,
            window->user_time_window,
            window->display->atom__NET_WM_USER_TIME,
            initial);
        }
    }
}

#define MAX_TITLE_LENGTH 512

/*
 * Called by set_window_title and set_icon_title to set the value of
 * *target to title. It required and atom is set, it will update the
 * appropriate property.
 *
 * Returns TRUE if a new title was set.
 */
static gboolean
set_title_text (MetaWindow  *window,
                gboolean     previous_was_modified,
                const char  *title,
                Atom         atom,
                char       **target)
{
  char hostname[HOST_NAME_MAX + 1];
  gboolean modified = FALSE;
  
  if (!target)
    return FALSE;
  
  g_free (*target);
  
  if (!title)
    *target = g_strdup ("");
  else if (g_utf8_strlen (title, MAX_TITLE_LENGTH + 1) > MAX_TITLE_LENGTH)
    {
      *target = meta_g_utf8_strndup (title, MAX_TITLE_LENGTH);
      modified = TRUE;
    }
  /* if WM_CLIENT_MACHINE indicates this machine is on a remote host
   * lets place that hostname in the title */
  else if (window->wm_client_machine &&
           !gethostname (hostname, HOST_NAME_MAX + 1) &&
           strcmp (hostname, window->wm_client_machine))
    {
      *target = g_strdup_printf (_("%s (on %s)"),
                      title, window->wm_client_machine);
      modified = TRUE;
    }
  else
    *target = g_strdup (title);

  if (modified && atom != None)
    meta_prop_set_utf8_string_hint (window->display,
                                    window->xwindow,
                                    atom, *target);

  /* Bug 330671 -- Don't forget to clear _NET_WM_VISIBLE_(ICON_)NAME */
  if (!modified && previous_was_modified)
    {
      meta_error_trap_push (window->display);
      XDeleteProperty (window->display->xdisplay,
                       window->xwindow,
                       atom);
      meta_error_trap_pop (window->display);
    }

  return modified;
}

static void
set_window_title (MetaWindow *window,
                  const char *title)
{
  char *str;
 
  gboolean modified =
    set_title_text (window,
                    window->using_net_wm_visible_name,
                    title,
                    window->display->atom__NET_WM_VISIBLE_NAME,
                    &window->title);
  window->using_net_wm_visible_name = modified;
  
  /* strndup is a hack since GNU libc has broken %.10s */
  str = g_strndup (window->title, 10);
  g_free (window->desc);
  window->desc = g_strdup_printf ("0x%lx (%s)", window->xwindow, str);
  g_free (str);

  if (window->frame)
    meta_ui_set_frame_title (window->screen->ui,
                             window->frame->xwindow,
                             window->title);

  g_object_notify (G_OBJECT (window), "title");
}

static void
reload_net_wm_name (MetaWindow    *window,
                    MetaPropValue *value,
                    gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      set_window_title (window, value->v.str);
      window->using_net_wm_name = TRUE;

      meta_verbose ("Using _NET_WM_NAME for new title of %s: \"%s\"\n",
                    window->desc, window->title);
    }
  else
    {
      set_window_title (window, NULL);
      window->using_net_wm_name = FALSE;
      if (!initial)
        meta_window_reload_property (window, XA_WM_NAME, FALSE);
    }
}

static void
reload_wm_name (MetaWindow    *window,
                MetaPropValue *value,
                gboolean       initial)
{
  if (window->using_net_wm_name)
    {
      meta_verbose ("Ignoring WM_NAME \"%s\" as _NET_WM_NAME is set\n",
                    value->v.str);
      return;
    }
  
  if (value->type != META_PROP_VALUE_INVALID)
    {
      set_window_title (window, value->v.str);

      meta_verbose ("Using WM_NAME for new title of %s: \"%s\"\n",
                    window->desc, window->title);
    }
  else
    {
      set_window_title (window, NULL);
    }
}

static void
reload_muffin_hints (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      char     *new_hints = value->v.str;
      char     *old_hints = window->muffin_hints;
      gboolean  changed   = FALSE;

      if (new_hints)
        {
          if (!old_hints || strcmp (new_hints, old_hints))
            changed = TRUE;
        }
      else
        {
          if (old_hints)
            changed = TRUE;
        }

      if (changed)
        {
          g_free (old_hints);

          if (new_hints)
            window->muffin_hints = g_strdup (new_hints);
          else
            window->muffin_hints = NULL;

          g_object_notify (G_OBJECT (window), "muffin-hints");
        }
    }
  else if (window->muffin_hints)
    {
      g_free (window->muffin_hints);
      window->muffin_hints = NULL;

      g_object_notify (G_OBJECT (window), "muffin-hints");
    }
}

static void
set_icon_title (MetaWindow *window,
                const char *title)
{
  gboolean modified =
    set_title_text (window,
                    window->using_net_wm_visible_icon_name,
                    title,
                    window->display->atom__NET_WM_VISIBLE_ICON_NAME,
                    &window->icon_name);
  window->using_net_wm_visible_icon_name = modified;
}

static void
reload_net_wm_icon_name (MetaWindow    *window,
                         MetaPropValue *value,
                         gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      set_icon_title (window, value->v.str);
      window->using_net_wm_icon_name = TRUE;

      meta_verbose ("Using _NET_WM_ICON_NAME for new title of %s: \"%s\"\n",
                    window->desc, window->title);
    }
  else
    {
      set_icon_title (window, NULL);
      window->using_net_wm_icon_name = FALSE;
      if (!initial)
        meta_window_reload_property (window, XA_WM_ICON_NAME, FALSE);
    }
}

static void
reload_wm_icon_name (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  if (window->using_net_wm_icon_name)
    {
      meta_verbose ("Ignoring WM_ICON_NAME \"%s\" as _NET_WM_ICON_NAME is set\n",
                    value->v.str);
      return;
    }
  
  if (value->type != META_PROP_VALUE_INVALID)
    {
      set_icon_title (window, value->v.str);
      
      meta_verbose ("Using WM_ICON_NAME for new title of %s: \"%s\"\n",
                    window->desc, window->title);
    }
  else
    {
      set_icon_title (window, NULL);
    }
}

static void
reload_net_wm_state (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  int i;

  /* We know this is only an initial window creation,
   * clients don't change the property.
   */

  if (!initial) {
    /* no, they DON'T change the property */
    meta_verbose ("Ignoring _NET_WM_STATE: we should be the one who set "
                  "the property in the first place\n");
    return;
  }
  window->shaded = FALSE;
  window->maximized_horizontally = FALSE;
  window->maximized_vertically = FALSE;
  window->fullscreen = FALSE;
  window->wm_state_modal = FALSE;
  window->wm_state_skip_taskbar = FALSE;
  window->wm_state_skip_pager = FALSE;
  window->wm_state_above = FALSE;
  window->wm_state_below = FALSE;
  window->wm_state_demands_attention = FALSE;

  if (value->type == META_PROP_VALUE_INVALID)
    return;

  i = 0;
  while (i < value->v.atom_list.n_atoms)
    {
      if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_TILED)
        window->tile_after_placement = TRUE;
      ++i;
    }

  i = 0;
  while (i < value->v.atom_list.n_atoms)
    {
      if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SHADED)
        window->shaded = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_HORZ &&
               !window->tile_after_placement)
        window->maximize_horizontally_after_placement = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_VERT &&
               !window->tile_after_placement)
        window->maximize_vertically_after_placement = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_HIDDEN)
        window->minimize_after_placement = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MODAL)
        window->wm_state_modal = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_TASKBAR)
        window->wm_state_skip_taskbar = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_PAGER)
        window->wm_state_skip_pager = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_FULLSCREEN)
        window->fullscreen_after_placement = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_ABOVE)
        window->wm_state_above = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_BELOW)
        window->wm_state_below = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_DEMANDS_ATTENTION)
        window->wm_state_demands_attention = TRUE;
      else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_STICKY)
        window->on_all_workspaces_requested = TRUE;
      ++i;
    }

  meta_verbose ("Reloaded _NET_WM_STATE for %s\n",
                window->desc);

  meta_window_recalc_window_type (window);
  meta_window_recalc_features (window);
}

static void
reload_mwm_hints (MetaWindow    *window,
                  MetaPropValue *value,
                  gboolean       initial)
{
  MotifWmHints *hints;
  gboolean old_decorated = window->decorated;

  window->mwm_decorated = TRUE;
  window->mwm_border_only = FALSE;
  window->mwm_has_close_func = TRUE;
  window->mwm_has_minimize_func = TRUE;
  window->mwm_has_maximize_func = TRUE;
  window->mwm_has_move_func = TRUE;
  window->mwm_has_resize_func = TRUE;

  if (value->type == META_PROP_VALUE_INVALID)
    {
      meta_verbose ("Window %s has no MWM hints\n", window->desc);
      meta_window_recalc_features (window);
      return;
    }

  hints = value->v.motif_hints;

  /* We support those MWM hints deemed non-stupid */

  meta_verbose ("Window %s has MWM hints\n",
                window->desc);

  if (hints->flags & MWM_HINTS_DECORATIONS)
    {
      meta_verbose ("Window %s sets MWM_HINTS_DECORATIONS 0x%lx\n",
          window->desc, hints->decorations);

      if (hints->decorations == 0)
        window->mwm_decorated = FALSE;
      /* some input methods use this */
      else if (hints->decorations == MWM_DECOR_BORDER)
        window->mwm_border_only = TRUE;
    }
  else
    meta_verbose ("Decorations flag unset\n");

  if (hints->flags & MWM_HINTS_FUNCTIONS)
    {
      gboolean toggle_value;

      meta_verbose ("Window %s sets MWM_HINTS_FUNCTIONS 0x%lx\n",
                    window->desc, hints->functions);

      /* If _ALL is specified, then other flags indicate what to turn off;
       * if ALL is not specified, flags are what to turn on.
       * at least, I think so
       */

      if ((hints->functions & MWM_FUNC_ALL) == 0)
        {
          toggle_value = TRUE;

          meta_verbose ("Window %s disables all funcs then reenables some\n",
                        window->desc);
          window->mwm_has_close_func = FALSE;
          window->mwm_has_minimize_func = FALSE;
          window->mwm_has_maximize_func = FALSE;
          window->mwm_has_move_func = FALSE;
          window->mwm_has_resize_func = FALSE;
        }
      else
        {
          meta_verbose ("Window %s enables all funcs then disables some\n",
                        window->desc);
          toggle_value = FALSE;
        }

      if ((hints->functions & MWM_FUNC_CLOSE) != 0)
        {
          meta_verbose ("Window %s toggles close via MWM hints\n",
                        window->desc);
          window->mwm_has_close_func = toggle_value;
        }
      if ((hints->functions & MWM_FUNC_MINIMIZE) != 0)
        {
          meta_verbose ("Window %s toggles minimize via MWM hints\n",
                        window->desc);
          window->mwm_has_minimize_func = toggle_value;
        }
      if ((hints->functions & MWM_FUNC_MAXIMIZE) != 0)
        {
          meta_verbose ("Window %s toggles maximize via MWM hints\n",
                        window->desc);
          window->mwm_has_maximize_func = toggle_value;
        }
      if ((hints->functions & MWM_FUNC_MOVE) != 0)
        {
          meta_verbose ("Window %s toggles move via MWM hints\n",
                        window->desc);
          window->mwm_has_move_func = toggle_value;
        }
      if ((hints->functions & MWM_FUNC_RESIZE) != 0)
        {
          meta_verbose ("Window %s toggles resize via MWM hints\n",
                        window->desc);
          window->mwm_has_resize_func = toggle_value;
        }
    }
  else
    meta_verbose ("Functions flag unset\n");

  meta_window_recalc_features (window);
  
  /* We do all this anyhow at the end of meta_window_new() */
  if (!window->constructing)
    {
      if (window->decorated)
        meta_window_ensure_frame (window);
      else
        meta_window_destroy_frame (window);
      
      meta_window_queue (window,
                         META_QUEUE_MOVE_RESIZE |
                         /* because ensure/destroy frame may unmap: */
                         META_QUEUE_CALC_SHOWING);

      if (old_decorated != window->decorated)
        g_object_notify (G_OBJECT (window), "decorated");
    }
}

static void
reload_wm_class (MetaWindow    *window,
                 MetaPropValue *value,
                 gboolean       initial)
{
  if (window->res_class)
    g_free (window->res_class);
  if (window->res_name)
    g_free (window->res_name);

  window->res_class = NULL;
  window->res_name = NULL;

  if (value->type != META_PROP_VALUE_INVALID)
    { 
      if (value->v.class_hint.res_name)
        window->res_name = g_strdup (value->v.class_hint.res_name);

      if (value->v.class_hint.res_class)
        window->res_class = g_strdup (value->v.class_hint.res_class);

      g_object_notify (G_OBJECT (window), "wm-class");
    }

  meta_verbose ("Window %s class: '%s' name: '%s'\n",
      window->desc,
      window->res_class ? window->res_class : "none",
      window->res_name ? window->res_name : "none");
}

static void
reload_net_wm_desktop (MetaWindow    *window,
                       MetaPropValue *value,
                       gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      window->initial_workspace_set = TRUE;
      window->initial_workspace = value->v.cardinal;
      meta_topic (META_DEBUG_PLACEMENT,
                  "Read initial workspace prop %d for %s\n",
                  window->initial_workspace, window->desc);
    }
}

static void
reload_net_startup_id (MetaWindow    *window,
                       MetaPropValue *value,
                       gboolean       initial)
{
  guint32 timestamp = window->net_wm_user_time;
  MetaWorkspace *workspace = NULL;
  
  g_free (window->startup_id);
  
  if (value->type != META_PROP_VALUE_INVALID)
    window->startup_id = g_strdup (value->v.str);
  else
    window->startup_id = NULL;
    
  /* Update timestamp and workspace on a running window */
  if (!window->constructing)
  {
    window->initial_timestamp_set = 0;  
    window->initial_workspace_set = 0;
    
    if (meta_screen_apply_startup_properties (window->screen, window))
      {
  
        if (window->initial_timestamp_set)
          timestamp = window->initial_timestamp;
        if (window->initial_workspace_set)
          workspace = meta_screen_get_workspace_by_index (window->screen, window->initial_workspace);
    
        meta_window_activate_with_workspace (window, timestamp, workspace);
      }
  }
  
  meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n",
                window->startup_id ? window->startup_id : "unset",
                window->desc);
}

static void
reload_update_counter (MetaWindow    *window,
                       MetaPropValue *value,
                       gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      meta_window_destroy_sync_request_alarm (window);
      window->sync_request_counter = None;

#ifdef HAVE_XSYNC
      if (value->v.xcounter_list.n_counters == 0)
        {
          meta_warning ("_NET_WM_SYNC_REQUEST_COUNTER is empty\n");
          return;
        }

      if (value->v.xcounter_list.n_counters == 1)
        {
          window->sync_request_counter = value->v.xcounter_list.counters[0];
          window->extended_sync_request_counter = FALSE;
        }
      else
        {
          window->sync_request_counter = value->v.xcounter_list.counters[1];
          window->extended_sync_request_counter = TRUE;
        }
      meta_verbose ("Window has _NET_WM_SYNC_REQUEST_COUNTER 0x%lx (extended=%s)\n",
                    window->sync_request_counter,
                    window->extended_sync_request_counter ? "true" : "false");

      if (window->extended_sync_request_counter)
        meta_window_create_sync_request_alarm (window);
#endif
    }
}

#define FLAG_TOGGLED_ON(old,new,flag) \
 (((old)->flags & (flag)) == 0 &&     \
  ((new)->flags & (flag)) != 0)

#define FLAG_TOGGLED_OFF(old,new,flag) \
 (((old)->flags & (flag)) != 0 &&      \
  ((new)->flags & (flag)) == 0)

#define FLAG_CHANGED(old,new,flag) \
  (FLAG_TOGGLED_ON(old,new,flag) || FLAG_TOGGLED_OFF(old,new,flag))

static void
spew_size_hints_differences (const XSizeHints *old,
                             const XSizeHints *new)
{
  if (FLAG_CHANGED (old, new, USPosition))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USPosition now %s\n",
                FLAG_TOGGLED_ON (old, new, USPosition) ? "set" : "unset");
  if (FLAG_CHANGED (old, new, USSize))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USSize now %s\n",
                FLAG_TOGGLED_ON (old, new, USSize) ? "set" : "unset");
  if (FLAG_CHANGED (old, new, PPosition))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PPosition now %s\n",
                FLAG_TOGGLED_ON (old, new, PPosition) ? "set" : "unset");
  if (FLAG_CHANGED (old, new, PSize))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PSize now %s\n",
                FLAG_TOGGLED_ON (old, new, PSize) ? "set" : "unset");
  if (FLAG_CHANGED (old, new, PMinSize))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMinSize now %s (%d x %d -> %d x %d)\n",
                FLAG_TOGGLED_ON (old, new, PMinSize) ? "set" : "unset",
                old->min_width, old->min_height,
                new->min_width, new->min_height);
  if (FLAG_CHANGED (old, new, PMaxSize))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMaxSize now %s (%d x %d -> %d x %d)\n",
                FLAG_TOGGLED_ON (old, new, PMaxSize) ? "set" : "unset",
                old->max_width, old->max_height,
                new->max_width, new->max_height);
  if (FLAG_CHANGED (old, new, PResizeInc))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PResizeInc now %s (width_inc %d -> %d height_inc %d -> %d)\n",
                FLAG_TOGGLED_ON (old, new, PResizeInc) ? "set" : "unset",
                old->width_inc, new->width_inc,
                old->height_inc, new->height_inc);
  if (FLAG_CHANGED (old, new, PAspect))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PAspect now %s (min %d/%d -> %d/%d max %d/%d -> %d/%d)\n",
                FLAG_TOGGLED_ON (old, new, PAspect) ? "set" : "unset",
                old->min_aspect.x, old->min_aspect.y,
                new->min_aspect.x, new->min_aspect.y,
                old->max_aspect.x, old->max_aspect.y,
                new->max_aspect.x, new->max_aspect.y);
  if (FLAG_CHANGED (old, new, PBaseSize))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PBaseSize now %s (%d x %d -> %d x %d)\n",
                FLAG_TOGGLED_ON (old, new, PBaseSize) ? "set" : "unset",
                old->base_width, old->base_height,
                new->base_width, new->base_height);
  if (FLAG_CHANGED (old, new, PWinGravity))
    meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PWinGravity now %s  (%d -> %d)\n",
                FLAG_TOGGLED_ON (old, new, PWinGravity) ? "set" : "unset",
                old->win_gravity, new->win_gravity);  
}

LOCAL_SYMBOL void
meta_set_normal_hints (MetaWindow *window,
                       XSizeHints *hints)
{
  int x, y, w, h;
  double minr, maxr;
  gint ui_scale;

  /* Some convenience vars */
  int minw, minh, maxw, maxh;   /* min/max width/height                      */
  int basew, baseh, winc, hinc; /* base width/height, width/height increment */

  ui_scale = meta_prefs_get_ui_scale ();

  /* Save the last ConfigureRequest, which we put here.
   * Values here set in the hints are supposed to
   * be ignored.
   */
  x = window->size_hints.x;
  y = window->size_hints.y;
  w = window->size_hints.width;
  h = window->size_hints.height;

  /* as far as I can tell, value->v.size_hints.flags is just to
   * check whether we had old-style normal hints without gravity,
   * base size as returned by XGetNormalHints(), so we don't
   * really use it as we fixup window->size_hints to have those
   * fields if they're missing.
   */

  /*
   * When the window is first created, NULL hints will
   * be passed in which will initialize all of the fields
   * as if flags were zero
   */
  if (hints)
    window->size_hints = *hints;
  else
    window->size_hints.flags = 0;

  /* Put back saved ConfigureRequest. */
  window->size_hints.x = x;
  window->size_hints.y = y;
  window->size_hints.width = w;
  window->size_hints.height = h;

  /* Get base size hints */
  if (window->size_hints.flags & PBaseSize)
    {
      meta_topic (META_DEBUG_GEOMETRY, "Window %s sets base size %d x %d\n",
                  window->desc,
                  window->size_hints.base_width,
                  window->size_hints.base_height);
    }
  else if (window->size_hints.flags & PMinSize)
    {
      window->size_hints.base_width = window->size_hints.min_width;
      window->size_hints.base_height = window->size_hints.min_height;
    }
  else
    {
      window->size_hints.base_width = 0;
      window->size_hints.base_height = 0;
    }
  window->size_hints.flags |= PBaseSize;

  /* Get min size hints */
  if (window->size_hints.flags & PMinSize)
    {
      meta_topic (META_DEBUG_GEOMETRY, "Window %s sets min size %d x %d\n",
                  window->desc,
                  window->size_hints.min_width,
                  window->size_hints.min_height);
    }
  else if (window->size_hints.flags & PBaseSize)
    {
      window->size_hints.min_width = window->size_hints.base_width;
      window->size_hints.min_height = window->size_hints.base_height;
    }
  else
    {
      window->size_hints.min_width = 0;
      window->size_hints.min_height = 0;
    }
  window->size_hints.flags |= PMinSize;

  /* Get max size hints */
  if (window->size_hints.flags & PMaxSize)
    {
      meta_topic (META_DEBUG_GEOMETRY, "Window %s sets max size %d x %d\n",
                  window->desc,
                  window->size_hints.max_width,
                  window->size_hints.max_height);
    }
  else
    {
      window->size_hints.max_width = G_MAXINT;
      window->size_hints.max_height = G_MAXINT;
      window->size_hints.flags |= PMaxSize;
    }

  /* Get resize increment hints */
  if (window->size_hints.flags & PResizeInc)
    {
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets resize width inc: %d height inc: %d\n",
                  window->desc,
                  window->size_hints.width_inc,
                  window->size_hints.height_inc);
    }
  else
    {
      window->size_hints.width_inc = ui_scale;
      window->size_hints.height_inc = ui_scale;
      window->size_hints.flags |= PResizeInc;
    }

  /* Get aspect ratio hints */
  if (window->size_hints.flags & PAspect)
    {
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets min_aspect: %d/%d max_aspect: %d/%d\n",
                  window->desc,
                  window->size_hints.min_aspect.x,
                  window->size_hints.min_aspect.y,
                  window->size_hints.max_aspect.x,
                  window->size_hints.max_aspect.y);
    }
  else
    {
      window->size_hints.min_aspect.x = 1;
      window->size_hints.min_aspect.y = G_MAXINT;
      window->size_hints.max_aspect.x = G_MAXINT;
      window->size_hints.max_aspect.y = 1;
      window->size_hints.flags |= PAspect;
    }

  /* Get gravity hint */
  if (window->size_hints.flags & PWinGravity)
    {
      meta_topic (META_DEBUG_GEOMETRY, "Window %s sets gravity %d\n",
                  window->desc,
                  window->size_hints.win_gravity);
    }
  else
    {
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s doesn't set gravity, using NW\n",
                  window->desc);
      window->size_hints.win_gravity = NorthWestGravity;
      window->size_hints.flags |= PWinGravity;
    }

  /*** Lots of sanity checking ***/

  /* Verify all min & max hints are at least 1 pixel */
  if (window->size_hints.min_width < ui_scale)
    {
      /* someone is on crack */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets min width to 0, which makes no sense\n",
                  window->desc);
      window->size_hints.min_width = ui_scale;
    }
  if (window->size_hints.max_width < ui_scale)
    {
      /* another cracksmoker */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets max width to 0, which makes no sense\n",
                  window->desc);
      window->size_hints.max_width = ui_scale;
    }
  if (window->size_hints.min_height < ui_scale)
    {
      /* another cracksmoker */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets min height to 0, which makes no sense\n",
                  window->desc);
      window->size_hints.min_height = ui_scale;
    }
  if (window->size_hints.max_height < ui_scale)
    {
      /* another cracksmoker */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets max height to 0, which makes no sense\n",
                  window->desc);
      window->size_hints.max_height = ui_scale;
    }

  /* Verify size increment hints are at least 1 pixel */
  if (window->size_hints.width_inc < ui_scale)
    {
      /* app authors find so many ways to smoke crack */
      window->size_hints.width_inc = ui_scale;
      meta_topic (META_DEBUG_GEOMETRY, "Corrected too-small width_inc to window scale factor\n");
    }
  if (window->size_hints.height_inc < ui_scale)
    {
      /* another cracksmoker */
      window->size_hints.height_inc = ui_scale;
      meta_topic (META_DEBUG_GEOMETRY, "Corrected too-small height_inc to window scale factor\n");
    }
  /* divide by 0 cracksmokers; note that x & y in (min|max)_aspect are
   * numerator & denominator
   */
  if (window->size_hints.min_aspect.y < 1)
    window->size_hints.min_aspect.y = 1;
  if (window->size_hints.max_aspect.y < 1)
    window->size_hints.max_aspect.y = 1;

  minw  = window->size_hints.min_width;  minh  = window->size_hints.min_height;
  maxw  = window->size_hints.max_width;  maxh  = window->size_hints.max_height;
  basew = window->size_hints.base_width; baseh = window->size_hints.base_height;
  winc  = window->size_hints.width_inc;  hinc  = window->size_hints.height_inc;

  /* Make sure min and max size hints are consistent with the base + increment
   * size hints.  If they're not, it's not a real big deal, but it means the
   * effective min and max size are more restrictive than the application
   * specified values.
   */
  if ((minw - basew) % winc != 0)
    {
      /* Take advantage of integer division throwing away the remainder... */
      window->size_hints.min_width = basew + ((minw - basew)/winc + 1)*winc;

      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s has width_inc (%d) that does not evenly divide "
                  "min_width - base_width (%d - %d); thus effective "
                  "min_width is really %d\n",
                  window->desc,
                  winc, minw, basew, window->size_hints.min_width);
      minw = window->size_hints.min_width;
    }
  if (maxw != G_MAXINT && (maxw - basew) % winc != 0)
    {
      /* Take advantage of integer division throwing away the remainder... */
      window->size_hints.max_width = basew + ((maxw - basew)/winc)*winc;

      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s has width_inc (%d) that does not evenly divide "
                  "max_width - base_width (%d - %d); thus effective "
                  "max_width is really %d\n",
                  window->desc,
                  winc, maxw, basew, window->size_hints.max_width);
      maxw = window->size_hints.max_width;
    }
  if ((minh - baseh) % hinc != 0)
    {
      /* Take advantage of integer division throwing away the remainder... */
      window->size_hints.min_height = baseh + ((minh - baseh)/hinc + 1)*hinc;

      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s has height_inc (%d) that does not evenly divide "
                  "min_height - base_height (%d - %d); thus effective "
                  "min_height is really %d\n",
                  window->desc,
                  hinc, minh, baseh, window->size_hints.min_height);
      minh = window->size_hints.min_height;
    }
  if (maxh != G_MAXINT && (maxh - baseh) % hinc != 0)
    {
      /* Take advantage of integer division throwing away the remainder... */
      window->size_hints.max_height = baseh + ((maxh - baseh)/hinc)*hinc;

      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s has height_inc (%d) that does not evenly divide "
                  "max_height - base_height (%d - %d); thus effective "
                  "max_height is really %d\n",
                  window->desc,
                  hinc, maxh, baseh, window->size_hints.max_height);
      maxh = window->size_hints.max_height;
    }

  /* make sure maximum size hints are compatible with minimum size hints; min
   * size hints take precedence.
   */
  if (window->size_hints.max_width < window->size_hints.min_width)
    {
      /* another cracksmoker */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets max width %d less than min width %d, "
                  "disabling resize\n",
                  window->desc,
                  window->size_hints.max_width,
                  window->size_hints.min_width);
      maxw = window->size_hints.max_width = window->size_hints.min_width;
    }
  if (window->size_hints.max_height < window->size_hints.min_height)
    {
      /* another cracksmoker */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets max height %d less than min height %d, "
                  "disabling resize\n",
                  window->desc,
                  window->size_hints.max_height,
                  window->size_hints.min_height);
      maxh = window->size_hints.max_height = window->size_hints.min_height;
    }

  /* Make sure the aspect ratio hints are sane. */
  minr =         window->size_hints.min_aspect.x /
         (double)window->size_hints.min_aspect.y;
  maxr =         window->size_hints.max_aspect.x /
         (double)window->size_hints.max_aspect.y;
  if (minr > maxr)
    {
      /* another cracksmoker; not even minimally (self) consistent */
      meta_topic (META_DEBUG_GEOMETRY,
                  "Window %s sets min aspect ratio larger than max aspect "
                  "ratio; disabling aspect ratio constraints.\n",
                  window->desc);
      window->size_hints.min_aspect.x = 1;
      window->size_hints.min_aspect.y = G_MAXINT;
      window->size_hints.max_aspect.x = G_MAXINT;
      window->size_hints.max_aspect.y = 1;
    }
  else /* check consistency of aspect ratio hints with other hints */
    {
      if (minh > 0 && minr > (maxw / (double)minh))
        {
          /* another cracksmoker */
          meta_topic (META_DEBUG_GEOMETRY,
                      "Window %s sets min aspect ratio larger than largest "
                      "aspect ratio possible given min/max size constraints; "
                      "disabling min aspect ratio constraint.\n",
                      window->desc);
          window->size_hints.min_aspect.x = 1;
          window->size_hints.min_aspect.y = G_MAXINT;
        }
      if (maxr < (minw / (double)maxh))
        {
          /* another cracksmoker */
          meta_topic (META_DEBUG_GEOMETRY,
                      "Window %s sets max aspect ratio smaller than smallest "
                      "aspect ratio possible given min/max size constraints; "
                      "disabling max aspect ratio constraint.\n",
                      window->desc);
          window->size_hints.max_aspect.x = G_MAXINT;
          window->size_hints.max_aspect.y = 1;
        }
      /* FIXME: Would be nice to check that aspect ratios are
       * consistent with base and size increment constraints.
       */
    }
}

static void
reload_normal_hints (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  if (value->type != META_PROP_VALUE_INVALID)
    {
      XSizeHints old_hints;
  
      meta_topic (META_DEBUG_GEOMETRY, "Updating WM_NORMAL_HINTS for %s\n", window->desc);

      old_hints = window->size_hints;
  
      meta_set_normal_hints (window, value->v.size_hints.hints);
      
      spew_size_hints_differences (&old_hints, &window->size_hints);
      
      meta_window_recalc_features (window);

      if (!initial)
        meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
    }
}

static void
reload_wm_protocols (MetaWindow    *window,
                     MetaPropValue *value,
                     gboolean       initial)
{
  int i;
  
  window->take_focus = FALSE;
  window->delete_window = FALSE;
  window->net_wm_ping = FALSE;
  
  if (value->type == META_PROP_VALUE_INVALID)    
    return;

  i = 0;
  while (i < value->v.atom_list.n_atoms)
    {
      if (value->v.atom_list.atoms[i] ==
          window->display->atom_WM_TAKE_FOCUS)
        window->take_focus = TRUE;
      else if (value->v.atom_list.atoms[i] ==
               window->display->atom_WM_DELETE_WINDOW)
        window->delete_window = TRUE;
      else if (value->v.atom_list.atoms[i] ==
               window->display->atom__NET_WM_PING)
        window->net_wm_ping = TRUE;
      ++i;
    }
  
  meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n",
                window->startup_id ? window->startup_id : "unset",
                window->desc);
}

static void
reload_wm_hints (MetaWindow    *window,
                 MetaPropValue *value,
                 gboolean       initial)
{
  Window old_group_leader;
  gboolean old_urgent;

  old_group_leader = window->xgroup_leader;
  old_urgent = window->wm_hints_urgent;
  
  /* Fill in defaults */
  window->input = TRUE;
  window->initially_iconic = FALSE;
  window->xgroup_leader = None;
  window->wm_hints_pixmap = None;
  window->wm_hints_mask = None;
  window->wm_hints_urgent = FALSE;

  if (value->type != META_PROP_VALUE_INVALID)
    {
      const XWMHints *hints = value->v.wm_hints;
      
      if (hints->flags & InputHint)
        window->input = hints->input;

      if (hints->flags & StateHint)
        window->initially_iconic = (hints->initial_state == IconicState);

      if (hints->flags & WindowGroupHint)
        window->xgroup_leader = hints->window_group;

      if (hints->flags & IconPixmapHint)
        window->wm_hints_pixmap = hints->icon_pixmap;

      if (hints->flags & IconMaskHint)
        window->wm_hints_mask = hints->icon_mask;

      if (hints->flags & XUrgencyHint)
        window->wm_hints_urgent = TRUE;

      meta_verbose ("Read WM_HINTS input: %d iconic: %d group leader: 0x%lx pixmap: 0x%lx mask: 0x%lx\n",
                    window->input, window->initially_iconic,
                    window->xgroup_leader,
                    window->wm_hints_pixmap,
                    window->wm_hints_mask);
    }

  if (window->xgroup_leader != old_group_leader)
    {
      meta_verbose ("Window %s changed its group leader to 0x%lx\n",
                    window->desc, window->xgroup_leader);
      
      meta_window_group_leader_changed (window);
    }

  /*
   * Do not emit urgency notification on the inital property load
   */
  if (!initial && (window->wm_hints_urgent != old_urgent))
    g_object_notify (G_OBJECT (window), "urgent");

  /*
   * Do not emit signal for the initial property load, let the constructor to
   * take care of it once the MetaWindow is fully constructed.
   *
   * Only emit if the property is both changed and set.
   */
  if (!initial && window->wm_hints_urgent && !old_urgent)
    g_signal_emit_by_name (window->display, "window-marked-urgent", window);

  meta_icon_cache_property_changed (&window->icon_cache,
                                    window->display,
                                    XA_WM_HINTS);

  meta_window_queue (window, META_QUEUE_UPDATE_ICON | META_QUEUE_MOVE_RESIZE);
}

typedef struct
{
  MetaWindow *window;
  Window transient_for_xid;
} FindByLeaderData;

static void
find_window_by_client_leader (MetaScreen *screen,
                              MetaWindow *window,
                              gpointer user_data)
{
    FindByLeaderData *data = (FindByLeaderData *) user_data;

    if (data->window != NULL)
      {
        return;
      }

    if (window->xclient_leader == None)
      {
        return;
      }

    if (window->xclient_leader == data->transient_for_xid)
      {
        data->window = window;
      }
}

static MetaWindow *
lookup_parent_by_client_leader (MetaWindow *transient,
                                Window      xtransient_for)
{
    MetaWindow *parent;
    FindByLeaderData *data;

    data = g_new0 (FindByLeaderData, 1);

    data->transient_for_xid = xtransient_for;

    meta_screen_foreach_window (transient->screen,
                                (MetaScreenWindowFunc) find_window_by_client_leader,
                                data);

    if (data != NULL)
    {
      parent = data->window;
    }

    g_free (data);

    return parent;
}

static void
reload_transient_for (MetaWindow    *window,
                      MetaPropValue *value,
                      gboolean       initial)
{
  MetaWindow *parent = NULL;
  Window transient_for, old_transient_for;

  if (value->type != META_PROP_VALUE_INVALID)
    {
      transient_for = value->v.xwindow;

      parent = meta_display_lookup_x_window (window->display, transient_for);

      if (!parent)
        {
          /* QT programs appear to set the transient-for hint to the program's WM_CLIENT_LEADER
           * instead of the toplevel window's xid.  This causes muffin to throw out their transient
           * state.  Since we know the client leader id of existing windows, we can do a quick search
           * and match up to the correct parent so our stacking and fullscreen detection code works
           * properly */

          parent = lookup_parent_by_client_leader (window, transient_for);

          if (parent)
            {
              transient_for = parent->xwindow;

              meta_verbose ("WM_TRANSIENT_FOR was parent window 0x%lx's WM_CLIENT_LEADER. "
                            "using the xid instead for %s.\n", transient_for, window->desc);
            }
          else
            {
              meta_warning (_("Invalid WM_TRANSIENT_FOR window 0x%lx specified "
                              "for %s.\n"),
                            transient_for, window->desc);
              transient_for = None;
            }
        }

      /* Make sure there is not a loop */
      while (parent)
        {
          if (parent == window)
            {
              meta_warning (_("WM_TRANSIENT_FOR window 0x%lx for %s "
                              "would create loop.\n"),
                            transient_for, window->desc);
              transient_for = None;
              break;
            }

          parent = meta_display_lookup_x_window (parent->display,
                                                 parent->xtransient_for);
        }
    }
  else
    transient_for = None;

  if (transient_for == window->xtransient_for)
    return;

  if (meta_window_appears_focused (window) && window->xtransient_for != None)
    meta_window_propagate_focus_appearance (window, FALSE);

  old_transient_for = window->xtransient_for;
  window->xtransient_for = transient_for;

  window->transient_parent_is_root_window =
    window->xtransient_for == window->screen->xroot;

  if (window->xtransient_for != None)
    meta_verbose ("Window %s transient for 0x%lx (root = %d)\n", window->desc,
        window->xtransient_for, window->transient_parent_is_root_window);
  else
    meta_verbose ("Window %s is not transient\n", window->desc);

  /* may now be a dialog */
  meta_window_recalc_window_type (window);

  if (!window->constructing)
    {
      /* If the window attaches, detaches, or changes attached
       * parents, we need to destroy the MetaWindow and let a new one
       * be created (which happens as a side effect of
       * meta_window_unmanage()). The condition below is correct
       * because we know window->xtransient_for has changed.
       */
      if (window->attached || meta_window_should_attach_to_parent (window))
        {
          guint32 timestamp;

          window->xtransient_for = old_transient_for;
          timestamp = meta_display_get_current_time_roundtrip (window->display);
          meta_window_unmanage (window, timestamp);
          return;
        }
    }

  /* update stacking constraints */
  if (!window->override_redirect)
    meta_stack_update_transient (window->screen->stack, window);

  /* possibly change its group. We treat being a window's transient as
   * equivalent to making it your group leader, to work around shortcomings
   * in programs such as xmms-- see #328211.
   */
  if (window->xtransient_for != None &&
      window->xgroup_leader != None &&
      window->xtransient_for != window->xgroup_leader)
    meta_window_group_leader_changed (window);

  if (!window->constructing && !window->override_redirect)
    meta_window_queue (window, META_QUEUE_MOVE_RESIZE);

  if (meta_window_appears_focused (window) && window->xtransient_for != None)
    meta_window_propagate_focus_appearance (window, TRUE);
}

static void
reload_gtk_theme_variant (MetaWindow    *window,
                          MetaPropValue *value,
                          gboolean       initial)
{
  char     *requested_variant = NULL;
  char     *current_variant   = window->gtk_theme_variant;

  if (value->type != META_PROP_VALUE_INVALID)
    {
      requested_variant = value->v.str;
      meta_verbose ("Requested \"%s\" theme variant for window %s.\n",
                    requested_variant, window->desc);
    }

  if (g_strcmp0 (requested_variant, current_variant) != 0)
    {
      g_free (current_variant);

      window->gtk_theme_variant = g_strdup (requested_variant);

      if (window->frame)
        meta_ui_update_frame_style (window->screen->ui, window->frame->xwindow);
    }
}

static void
reload_bypass_compositor (MetaWindow    *window,
                          MetaPropValue *value,
                          gboolean       initial)
{
  int requested_value = 0;
  int current_value = window->bypass_compositor;

  if (value->type != META_PROP_VALUE_INVALID)
    {
      requested_value = (int) value->v.cardinal;
    }

  if (requested_value == current_value)
    return;

  if (requested_value == _NET_WM_BYPASS_COMPOSITOR_HINT_ON)
    meta_verbose ("Request to bypass compositor for window %s.\n", window->desc);
  else if (requested_value == _NET_WM_BYPASS_COMPOSITOR_HINT_OFF)
    meta_verbose ("Request to don't bypass compositor for window %s.\n", window->desc);
  else if (requested_value != _NET_WM_BYPASS_COMPOSITOR_HINT_AUTO)
    return;

  window->bypass_compositor = requested_value;
}

#define RELOAD_STRING(var_name, propname) \
  static void                                       \
  reload_ ## var_name (MetaWindow    *window,       \
                       MetaPropValue *value,        \
                       gboolean       initial)      \
  {                                                 \
    g_free (window->var_name);                      \
                                                    \
    if (value->type != META_PROP_VALUE_INVALID)     \
      window->var_name = g_strdup (value->v.str);   \
    else                                            \
      window->var_name = NULL;                      \
                                                    \
    g_object_notify (G_OBJECT (window), propname);  \
  }

RELOAD_STRING (gtk_unique_bus_name,         "gtk-unique-bus-name")
RELOAD_STRING (gtk_application_id,          "gtk-application-id")
RELOAD_STRING (gtk_application_object_path, "gtk-application-object-path")
RELOAD_STRING (gtk_window_object_path,      "gtk-window-object-path")
RELOAD_STRING (gtk_app_menu_object_path,    "gtk-app-menu-object-path")
RELOAD_STRING (gtk_menubar_object_path,     "gtk-menubar-object-path")

#undef RELOAD_STRING

/*
 * Initialises the property hooks system.  Each row in the table named "hooks"
 * represents an action to take when a property is found on a newly-created
 * window, or when a property changes its value.
 *
 * The first column shows which atom the row concerns.
 * The second gives the type of the property data.  The property will be
 * queried for its new value, unless the type is given as
 * META_PROP_VALUE_INVALID, in which case nothing will be queried.
 * The third column gives the name of a callback which gets called with the
 * new value.  (If the new value was not retrieved because the second column
 * was META_PROP_VALUE_INVALID, the callback still gets called anyway.)
 * This value may be NULL, in which case no callback will be called.
 */
LOCAL_SYMBOL void
meta_display_init_window_prop_hooks (MetaDisplay *display)
{
  /* INIT: load initially
   * O-R:  fetch for override-redirect windows
   *
   * The ordering here is significant for the properties we load
   * initially: they are roughly ordered in the order we want them to
   * be gotten. We want to get window name and class first so we can
   * use them in error messages and such. However, name is modified
   * depending on wm_client_machine, so push it slightly sooner.
   *
   * For override-redirect windows, we pay attention to:
   *
   *  - properties that identify the window: useful for debugging
   *    purposes.
   *  - NET_WM_WINDOW_TYPE: can be used to do appropriate handling
   *    for different types of override-redirect windows.
   */
  MetaWindowPropHooks hooks[] = {                                                       /* INIT   O-R     */
    { display->atom_WM_CLIENT_MACHINE, META_PROP_VALUE_STRING,   reload_wm_client_machine, TRUE,  TRUE },
    { display->atom__NET_WM_NAME,      META_PROP_VALUE_UTF8,     reload_net_wm_name,       TRUE,  TRUE },
    { XA_WM_CLASS,                     META_PROP_VALUE_CLASS_HINT, reload_wm_class,        TRUE,  TRUE },
    { display->atom__NET_WM_PID,       META_PROP_VALUE_CARDINAL, reload_net_wm_pid,        TRUE,  TRUE },
    { XA_WM_NAME,                      META_PROP_VALUE_TEXT_PROPERTY, reload_wm_name,      TRUE,  TRUE },
    { display->atom__MUFFIN_HINTS,     META_PROP_VALUE_TEXT_PROPERTY, reload_muffin_hints, TRUE,  TRUE },
    { display->atom__NET_WM_ICON_NAME, META_PROP_VALUE_UTF8,     reload_net_wm_icon_name,  TRUE,  FALSE },
    { XA_WM_ICON_NAME,                 META_PROP_VALUE_TEXT_PROPERTY, reload_wm_icon_name, TRUE,  FALSE },
    { display->atom__NET_WM_DESKTOP,   META_PROP_VALUE_CARDINAL, reload_net_wm_desktop,    TRUE,  FALSE },
    { display->atom__NET_STARTUP_ID,   META_PROP_VALUE_UTF8,     reload_net_startup_id,    TRUE,  FALSE },
    { display->atom__NET_WM_SYNC_REQUEST_COUNTER, META_PROP_VALUE_SYNC_COUNTER_LIST, reload_update_counter, TRUE, TRUE },
    { XA_WM_NORMAL_HINTS,              META_PROP_VALUE_SIZE_HINTS, reload_normal_hints,    TRUE,  FALSE },
    { display->atom_WM_PROTOCOLS,      META_PROP_VALUE_ATOM_LIST, reload_wm_protocols,     TRUE,  FALSE },
    { XA_WM_HINTS,                     META_PROP_VALUE_WM_HINTS,  reload_wm_hints,         TRUE,  FALSE },
    { display->atom__NET_WM_USER_TIME, META_PROP_VALUE_CARDINAL, reload_net_wm_user_time,  TRUE,  FALSE },
    { display->atom__NET_WM_STATE,     META_PROP_VALUE_ATOM_LIST, reload_net_wm_state,     TRUE,  FALSE },
    { display->atom__MOTIF_WM_HINTS,   META_PROP_VALUE_MOTIF_HINTS, reload_mwm_hints,      TRUE,  FALSE },
    { XA_WM_TRANSIENT_FOR,             META_PROP_VALUE_WINDOW,    reload_transient_for,    TRUE,  FALSE },
    { display->atom__GTK_THEME_VARIANT, META_PROP_VALUE_UTF8,     reload_gtk_theme_variant, TRUE, FALSE },    
    { display->atom__GTK_APPLICATION_ID,               META_PROP_VALUE_UTF8,         reload_gtk_application_id,               TRUE, FALSE },
    { display->atom__GTK_UNIQUE_BUS_NAME,              META_PROP_VALUE_UTF8,         reload_gtk_unique_bus_name,              TRUE, FALSE },
    { display->atom__GTK_APPLICATION_OBJECT_PATH,      META_PROP_VALUE_UTF8,         reload_gtk_application_object_path,      TRUE, FALSE },
    { display->atom__GTK_WINDOW_OBJECT_PATH,           META_PROP_VALUE_UTF8,         reload_gtk_window_object_path,           TRUE, FALSE },    
    { display->atom__GTK_APP_MENU_OBJECT_PATH,         META_PROP_VALUE_UTF8,         reload_gtk_app_menu_object_path,         TRUE, FALSE },    
    { display->atom__GTK_MENUBAR_OBJECT_PATH,          META_PROP_VALUE_UTF8,         reload_gtk_menubar_object_path,          TRUE, FALSE },
    { display->atom__GTK_FRAME_EXTENTS,                META_PROP_VALUE_CARDINAL_LIST,reload_gtk_frame_extents,                TRUE, FALSE },
    { display->atom__NET_WM_USER_TIME_WINDOW, META_PROP_VALUE_WINDOW, reload_net_wm_user_time_window, TRUE, FALSE },
    { display->atom_WM_STATE,          META_PROP_VALUE_INVALID,  NULL,                     FALSE, FALSE },
    { display->atom__NET_WM_ICON,      META_PROP_VALUE_INVALID,  reload_net_wm_icon,       FALSE, FALSE },
    { display->atom__KWM_WIN_ICON,     META_PROP_VALUE_INVALID,  reload_kwm_win_icon,      FALSE, FALSE },
    { display->atom__NET_WM_ICON_GEOMETRY, META_PROP_VALUE_CARDINAL_LIST, reload_icon_geometry, FALSE, FALSE },
    { display->atom_WM_CLIENT_LEADER,  META_PROP_VALUE_INVALID, complain_about_broken_client, FALSE, FALSE },
    { display->atom_SM_CLIENT_ID,      META_PROP_VALUE_INVALID, complain_about_broken_client, FALSE, FALSE },
    { display->atom_WM_WINDOW_ROLE,    META_PROP_VALUE_INVALID, reload_wm_window_role,        FALSE, FALSE },
    { display->atom__NET_WM_WINDOW_TYPE, META_PROP_VALUE_INVALID, reload_net_wm_window_type,  FALSE, TRUE },
    { display->atom__NET_WM_STRUT,         META_PROP_VALUE_INVALID, reload_struts,            FALSE, FALSE },
    { display->atom__NET_WM_STRUT_PARTIAL, META_PROP_VALUE_INVALID, reload_struts,            FALSE, FALSE },
    { display->atom__NET_WM_BYPASS_COMPOSITOR, META_PROP_VALUE_CARDINAL,  reload_bypass_compositor, TRUE, TRUE },
    { display->atom__NET_WM_XAPP_ICON_NAME, META_PROP_VALUE_UTF8,     reload_theme_icon_name, TRUE,  TRUE },
    { display->atom__NET_WM_XAPP_PROGRESS, META_PROP_VALUE_CARDINAL, reload_progress,         TRUE,  TRUE },
    { display->atom__NET_WM_XAPP_PROGRESS_PULSE, META_PROP_VALUE_CARDINAL, reload_progress_pulse, TRUE,  TRUE },
    { 0 },
  };

  MetaWindowPropHooks *table = g_memdup (hooks, sizeof (hooks)),
    *cursor = table;
  
  g_assert (display->prop_hooks == NULL);

  display->prop_hooks_table = (gpointer) table;
  display->prop_hooks = g_hash_table_new (NULL, NULL);

  while (cursor->property)
    {
      /* Doing initial loading doesn't make sense if we just want notification */
      g_assert (!(hooks->load_initially && hooks->type == META_PROP_VALUE_INVALID));

      /* Atoms are safe to use with GINT_TO_POINTER because it's safe with
       * anything 32 bits or less, and atoms are 32 bits with the top three
       * bits clear.  (Scheifler & Gettys, 2e, p372)
       */
      g_hash_table_insert (display->prop_hooks,
                           GINT_TO_POINTER (cursor->property),
                           cursor);
      cursor++;
    }
  display->n_prop_hooks = cursor - table;
}

LOCAL_SYMBOL void
meta_display_free_window_prop_hooks (MetaDisplay *display)
{
  g_hash_table_unref (display->prop_hooks);
  display->prop_hooks = NULL;

  g_free (display->prop_hooks_table);
  display->prop_hooks_table = NULL;
}

static MetaWindowPropHooks*
find_hooks (MetaDisplay *display,
            Atom         property)
{
  return g_hash_table_lookup (display->prop_hooks,
                              GINT_TO_POINTER (property));
}
