/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 This file is strongly based on the corresponding files from
 twm and enlightenment.
 */

/*#define FVWM_DEBUG_MSGS 1*/
/*#define FVWM_DEBUG_DEVEL 1*/

#include "config.h"

#include <stdio.h>
#ifdef HAVE_GETPWUID
#include <pwd.h>
#endif
#include <signal.h>
#include <fcntl.h>

#include "libs/fvwmlib.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "add_window.h"
#include "bindings.h"
#include "misc.h"
#include "screen.h"
#include "session.h"
#include "module_interface.h"
#include "stack.h"
#include "icccm2.h"
#include "virtual.h"
#include "geometry.h"
#ifdef SESSION
#include <X11/SM/SMlib.h>
#endif

extern int master_pid;


typedef struct _match
{
  unsigned long      win;
  char               *client_id;
  char               *res_name;
  char               *res_class;
  char               *window_role;
  char               *wm_name;
  int                wm_command_count;
  char               **wm_command;
  int                 x, y, w, h, icon_x, icon_y;
  int                 x_max, y_max, w_max, h_max;
  int                 width_defect_max, height_defect_max;
  int                 max_x_offset, max_y_offset;
  int                 desktop;
  int                 layer;
  int                 used;
  int                 gravity;
  window_flags        flags;
}
Match;

#ifdef SESSION
int sm_fd = -1;
static char *previous_sm_client_id = NULL;
static char *sm_client_id = NULL;
static Bool sent_save_done = 0;
#endif

static int num_match = 0;
static Match *matches = NULL;
static Bool does_file_version_match = False;

extern Bool Restarting;

static
char *duplicate(const char *s)
{
  int l;
  char *r;

  if (!s) return NULL;
  l = strlen(s);
  r = (char *) safemalloc (sizeof(char)*(l+1));
  strncpy(r, s, l+1);
  return r;
}

/*
 * It is a bit ugly to have a separate file format for
 * config files and session save files. The proper way
 * to do this may be to extend the config file format
 * to allow the specification of everything we need
 * to save here. Then the option "-restore xyz" could
 * be replaced by "-f xyz".
 */
static int
SaveGlobalState(FILE *f)
{
  fprintf(f, "[GLOBAL]\n");
  fprintf(f, "  [DESKTOP] %i\n", Scr.CurrentDesk);
  fprintf(f, "  [VIEWPORT] %i %i %i %i\n",
          Scr.Vx, Scr.Vy, Scr.VxMax, Scr.VyMax);
  fprintf(f, "  [SCROLL] %i %i %i %i %i %i\n",
	  Scr.EdgeScrollX, Scr.EdgeScrollY, Scr.ScrollResistance,
	  Scr.MoveResistance,
	  !!(Scr.flags.edge_wrap_x), !!(Scr.flags.edge_wrap_y));
  fprintf(f, "  [SNAP] %i %i %i %i\n",
	  Scr.SnapAttraction, Scr.SnapMode, Scr.SnapGridX, Scr.SnapGridY);
  fprintf(f, "  [MISC] %i %i %i\n",
	  Scr.ClickTime, Scr.ColormapFocus, Scr.ColorLimit);
  fprintf(f, "  [STYLE] %i %i\n", Scr.gs.EmulateMWM, Scr.gs.EmulateWIN);
  return 1;
}

static Bool doPreserveState = True;

#ifdef SESSION
extern char **g_argv;
extern int g_argc;

SmcConn sm_conn = NULL;
static char *realStateFilename = NULL;
static Bool goingToRestart = False;

static void
setSmProperties (SmcConn sm_conn, char *filename, char hint);

static void setRealStateFilename(char *filename)
{
  if (realStateFilename)
    free(realStateFilename);
  realStateFilename = safestrdup(filename);
}

static char *getUniqueStateFilename(void)
{
  const char *path = getenv("SM_SAVE_DIR");
  char *filename;
  int fd;

  if ( ! path)
    path = getenv ("HOME");

#ifdef HAVE_GETPWUID
  if (!path) {
    struct passwd *pwd = getpwuid(getuid());
    if (pwd)
      path = pwd->pw_dir;
  }
#endif
  if ( ! path)
    return NULL;

  filename = tempnam(path, ".fs-");
  if ((fd = open (filename, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1) {
    free (filename);
    filename = NULL;
  } else {
    close (fd);
  }
  return filename;
}

#else
#define setRealStateFilename(filename)
#endif /* SESSION */

void
LoadGlobalState(char *filename)
{
  FILE               *f;
  char                s[4096], s1[4096];
  /* char s2[256]; */
  int i1, i2, i3, i4, i5, i6;

  if (!does_file_version_match)
    return;
  if (!filename || !*filename)
    return;
  if ((f = fopen(filename, "r")) == NULL)
    return;

  while (fgets(s, sizeof(s), f))
  {
    i1 = 0; i2 = 0; i3 = 0; i4 = 0; i5 = 0; i6 = 0;
    sscanf(s, "%4000s", s1);
#ifdef SESSION
    /* If we are restarting, [REAL_STATE_FILENAME] points
     * to the file containing the true session state. */
    if (!strcmp(s1, "[REAL_STATE_FILENAME]"))
    {
    /* migo: temporarily (?) moved to LoadWindowStates (trick for gnome-session)
      sscanf(s, "%*s %s", s2);
      setSmProperties (sm_conn, s2, SmRestartIfRunning);
      setRealStateFilename(s2);
    */
    }
    else
#endif /* SESSION */
    if (!strcmp(s1, "[DESKTOP]"))
    {
      sscanf(s, "%*s %i", &i1);
      goto_desk(i1);
    }
    else if (!strcmp(s1, "[VIEWPORT]"))
    {
      sscanf(s, "%*s %i %i %i %i", &i1, &i2, &i3, &i4);
    /* migo: we don't want to lose DeskTopSize in configurations,
     * and it does not work well anyways - Gnome is not updated
      Scr.VxMax = i3;
      Scr.VyMax = i4;
    */
      MoveViewport(i1, i2, True);
    }
#if 0
    /* migo (08-Dec-1999): we don't want to eliminate .fvwm2rc for now */
    else if (/*!Restarting*/ 0)
    {
      /* Matthias: We don't want to restore too much state if
       * we are restarting, since that would make restarting
       * useless for rereading changed rc files. */
      if (!strcmp(s1, "[SCROLL]"))
      {
        sscanf(s, "%*s %i %i %i %i %i %i", &i1, &i2, &i3, &i4, &i5, &i6);
        Scr.EdgeScrollX = i1;
        Scr.EdgeScrollY = i2;
        Scr.ScrollResistance = i3;
        Scr.MoveResistance = i4;
        if (i5)
          Scr.flags.edge_wrap_x = 1;
        else
          Scr.flags.edge_wrap_x = 0;
        if (i6)
          Scr.flags.edge_wrap_y = 1;
        else
          Scr.flags.edge_wrap_y = 0;
      }
      else if (!strcmp(s1, "[SNAP]"))
      {
        sscanf(s, "%*s %i %i %i %i", &i1, &i2, &i3, &i4);
        Scr.SnapAttraction = i1;
        Scr.SnapMode = i2;
        Scr.SnapGridX = i3;
        Scr.SnapGridY = i4;
      }
      else if (!strcmp(s1, "[MISC]"))
      {
        sscanf(s, "%*s %i %i %i", &i1, &i2, &i3);
        Scr.ClickTime = i1;
        Scr.ColormapFocus = i2;
        Scr.ColorLimit = i3;
      }
      else if (!strcmp(s1, "[STYLE]"))
      {
        sscanf(s, "%*s %i %i", &i1, &i2);
        Scr.gs.EmulateMWM = i1;
        Scr.gs.EmulateWIN = i2;
      }
    }
#endif
  }
  fclose(f);
}

static char *
GetWindowRole(Window window)
{
  XTextProperty tp;

  if (XGetTextProperty (dpy, window, &tp, _XA_WM_WINDOW_ROLE))
  {
    if (tp.encoding == XA_STRING && tp.format == 8 && tp.nitems != 0)
      return ((char *) tp.value);
  }

  return NULL;
}

static char *
GetClientID(Window window)
{
  char *client_id = NULL;
  Window client_leader;
  XTextProperty tp;
  Atom actual_type;
  int actual_format;
  unsigned long nitems;
  unsigned long bytes_after;
  unsigned char *prop = NULL;

  if (XGetWindowProperty(dpy, window, _XA_WM_CLIENT_LEADER,
			 0L, 1L, False, AnyPropertyType, &actual_type,
			 &actual_format, &nitems, &bytes_after, &prop)
    == Success)
  {
    if (actual_type == XA_WINDOW && actual_format == 32 &&
        nitems == 1 && bytes_after == 0)
    {
      client_leader = *((Window *) prop);

      if (XGetTextProperty (dpy, client_leader, &tp, _XA_SM_CLIENT_ID))
      {
        if (tp.encoding == XA_STRING &&
            tp.format == 8 && tp.nitems != 0)
          client_id = (char *) tp.value;
      }
    }

    if (prop)
      XFree (prop);
  }

  return client_id;
}

char *get_version_string()
{
  /* migo (14-Mar-2001): it is better to manually update a version string
   * in the stable branch, othervise saving sessions becomes useless */
  /*return CatString3(VERSION, ", ",__DATE__);*/

  return "2.4-4";
}

/*
**  Verify the current fvwm version with the version that stroed the state file.
**  No state will be restored if versions don't match.
*/
static Bool VerifyVersionInfo(char *filename)
{
  FILE *f;
  char s[4096], s1[4096];

  if (!filename || !*filename)
    return False;
  if ((f = fopen(filename, "r")) == NULL)
    return False;

  while (fgets(s, sizeof(s), f))
  {
    sscanf(s, "%4000s", s1);
    if (!strcmp(s1, "[FVWM_VERSION]"))
    {
      char *current_v = get_version_string();
      sscanf(s, "%*s %[^\n]", s1);
      if (strcmp(s1, current_v) == 0)
      {
	does_file_version_match = True;
      }
      else
      {
	fvwm_msg(
	  ERR, "VerifyVersionInfo",
	  "State file version (%s) does not match the current version (%s)\n"
	  "State file will be ignored", s1, current_v);
	break;
      }
    }
  }
  fclose(f);

  return does_file_version_match;
}

static int
SaveVersionInfo(FILE *f)
{
  fprintf(f, "[FVWM_VERSION] %s\n", get_version_string());

  return 1;
}

char *unspace_string(const char *str)
{
  static const char *spaces = " \t\n";
  char *tr_str = CatString2(str, NULL);
  int i;

  if (!tr_str)
    return NULL;

  for (i = 0; i < strlen(spaces); i++)
  {
    char *ptr = tr_str;
    while ((ptr = strchr(ptr, spaces[i])) != NULL)
      *(ptr++) = '_';
  }
  return tr_str;
}

static int
SaveWindowStates(FILE *f)
{
  char *client_id;
  char *window_role;
  char **wm_command;
  int wm_command_count;
  FvwmWindow *ewin;
  rectangle save_g;
  int i;

  for (ewin = get_next_window_in_stack_ring(&Scr.FvwmRoot);
       ewin != &Scr.FvwmRoot;
       ewin = get_next_window_in_stack_ring(ewin))
  {
    Bool is_icon_sticky;

    if (!XGetGeometry(dpy, ewin->w, &JunkRoot, &JunkX, &JunkY, &JunkWidth,
		      &JunkHeight, &JunkBW, &JunkDepth))
    {
      /* Don't save the state of windows that already died (i.e. modules)!
       */
      continue;
    }
    is_icon_sticky = (IS_STICKY(ewin) ||
		     (IS_ICONIFIED(ewin) && IS_ICON_STICKY(ewin)));
    fprintf(f, "[CLIENT] %lx\n", ewin->w);

    client_id = GetClientID(ewin->w);
    if (client_id)
    {
      fprintf(f, "  [CLIENT_ID] %s\n", client_id);
      XFree(client_id);
    }

    window_role = GetWindowRole(ewin->w);
    if (window_role)
    {
      fprintf(f, "  [WINDOW_ROLE] %s\n", window_role);
      XFree(window_role);
    }
    else
    {
      if (ewin->class.res_class)
        fprintf(f, "  [RES_NAME] %s\n", ewin->class.res_name);
      if (ewin->class.res_name)
        fprintf(f, "  [RES_CLASS] %s\n", ewin->class.res_class);
      if (ewin->name)
        fprintf(f, "  [WM_NAME] %s\n", ewin->name);

      wm_command = NULL;
      wm_command_count = 0;
      if (XGetCommand(dpy, ewin->w, &wm_command, &wm_command_count) &&
	  wm_command && wm_command_count > 0)
      {
        fprintf(f, "  [WM_COMMAND] %i", wm_command_count);
        for (i = 0; i < wm_command_count; i++)
          fprintf(f, " %s", unspace_string(wm_command[i]));
        fprintf(f, "\n");
      }
      if (wm_command)
      {
        XFreeStringList(wm_command);
	wm_command = NULL;
      }
    } /* !window_role */

    gravity_get_naked_geometry(
      ewin->hints.win_gravity, ewin, &save_g, &ewin->normal_g);
    if (IS_STICKY(ewin))
    {
      save_g.x -= Scr.Vx;
      save_g.y -= Scr.Vy;
    }
    fprintf(
      f, "  [GEOMETRY] %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i\n",
      save_g.x, save_g.y, save_g.width, save_g.height,
      ewin->max_g.x, ewin->max_g.y, ewin->max_g.width, ewin->max_g.height,
      ewin->max_g_defect.width, ewin->max_g_defect.height,
      ewin->icon_g.x + ((!is_icon_sticky) ? Scr.Vx : 0),
      ewin->icon_g.y + ((!is_icon_sticky) ? Scr.Vy : 0),
      ewin->hints.win_gravity,
      ewin->max_offset.x, ewin->max_offset.y);
    fprintf(f, "  [DESK] %i\n", ewin->Desk);
    fprintf(f, "  [LAYER] %i\n", get_layer(ewin));
    fprintf(f, "  [FLAGS] ");
    for (i = 0; i < sizeof(window_flags); i++)
      fprintf(f, "%02x ", (int)(((unsigned char *)&(ewin->flags))[i]));
    fprintf(f, "\n");
  }
  return 1;
}

void
DisableRestoringState(void)
{
  num_match = 0;
  return;
}

void
LoadWindowStates(char *filename)
{
  FILE *f;
  char s[4096], s1[4096];
  char *s2;
  int i, pos, pos1;
  unsigned long w;
  int n;

  if (!VerifyVersionInfo(filename))
    return;

  if (!filename || !*filename)
    return;

  setRealStateFilename(filename);

  if ((f = fopen(filename, "r")) == NULL)
    return;

#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\tLoading %s\n", filename);
#endif
#ifdef FVWM_DEBUG_DEVEL
  system(CatString3("mkdir -p /tmp/fs-load; cp ", filename, " /tmp/fs-load"));
#endif

  while (fgets(s, sizeof(s), f))
  {
    n = 0;
    sscanf(s, "%4000s%n", s1, &n);
#ifdef SESSION
    /* migo: temporarily */
    if (!strcmp(s1, "[REAL_STATE_FILENAME]"))
    {
      sscanf(s, "%*s %s", s1);
      setSmProperties (sm_conn, s1, SmRestartIfRunning);
      setRealStateFilename(s1);
    } else
#endif /* SESSION */
    if (!strcmp(s1, "[CLIENT]"))
    {
      sscanf(s, "%*s %lx", &w);
      num_match++;
      matches =
	(Match *)saferealloc((void *)matches, sizeof(Match) * num_match);
      matches[num_match - 1].win = w;
      matches[num_match - 1].client_id = NULL;
      matches[num_match - 1].res_name = NULL;
      matches[num_match - 1].res_class = NULL;
      matches[num_match - 1].window_role = NULL;
      matches[num_match - 1].wm_name = NULL;
      matches[num_match - 1].wm_command_count = 0;
      matches[num_match - 1].wm_command = NULL;
      matches[num_match - 1].x = 0;
      matches[num_match - 1].y = 0;
      matches[num_match - 1].w = 100;
      matches[num_match - 1].h = 100;
      matches[num_match - 1].x_max = 0;
      matches[num_match - 1].y_max = 0;
      matches[num_match - 1].w_max = Scr.MyDisplayWidth;
      matches[num_match - 1].h_max = Scr.MyDisplayHeight;
      matches[num_match - 1].width_defect_max = 0;
      matches[num_match - 1].height_defect_max = 0;
      matches[num_match - 1].icon_x = 0;
      matches[num_match - 1].icon_y = 0;
      matches[num_match - 1].desktop = 0;
      matches[num_match - 1].layer = 0;
      memset(&(matches[num_match - 1].flags), 0,
	     sizeof(window_flags));
      matches[num_match - 1].used = 0;
    }
    else if (!strcmp(s1, "[GEOMETRY]"))
    {
      sscanf(s, "%*s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i",
	     &(matches[num_match - 1].x),
	     &(matches[num_match - 1].y),
	     &(matches[num_match - 1].w),
	     &(matches[num_match - 1].h),
	     &(matches[num_match - 1].x_max),
	     &(matches[num_match - 1].y_max),
	     &(matches[num_match - 1].w_max),
	     &(matches[num_match - 1].h_max),
	     &(matches[num_match - 1].width_defect_max),
	     &(matches[num_match - 1].height_defect_max),
	     &(matches[num_match - 1].icon_x),
	     &(matches[num_match - 1].icon_y),
	     &(matches[num_match - 1].gravity),
	     &(matches[num_match - 1].max_x_offset),
	     &(matches[num_match - 1].max_y_offset));
    }
    else if (!strcmp(s1, "[DESK]"))
    {
      sscanf(s, "%*s %i",
	     &(matches[num_match - 1].desktop));
    }
    else if (!strcmp(s1, "[LAYER]"))
    {
      sscanf(s, "%*s %i",
	     &(matches[num_match - 1].layer));
    }
    else if (!strcmp(s1, "[FLAGS]"))
    {
      char *ts = s;

      /* skip [FLAGS] */
      while (*ts != ']')
	ts++;
      ts++;

      for (i = 0; i < sizeof(window_flags); i++)
      {
        unsigned int f;
        sscanf(ts, "%02x ", &f);
        ((unsigned char *)&(matches[num_match-1].flags))[i]
          = f;
        ts += 3;
      }
    }
    else if (!strcmp(s1, "[CLIENT_ID]"))
    {
      s2 = s + n;
      if (*s2 != 0)
      {
	s2++;
      }
      sscanf(s2, "%[^\n]", s1);
      matches[num_match - 1].client_id = duplicate(s1);
    }
    else if (!strcmp(s1, "[WINDOW_ROLE]"))
    {
      s2 = s + n;
      if (*s2 != 0)
      {
	s2++;
      }
      sscanf(s2, "%[^\n]", s1);
      matches[num_match - 1].window_role = duplicate(s1);
    }
    else if (!strcmp(s1, "[RES_NAME]"))
    {
      s2 = s + n;
      if (*s2 != 0)
      {
	s2++;
      }
      sscanf(s2, "%[^\n]", s1);
      matches[num_match - 1].res_name = duplicate(s1);
    }
    else if (!strcmp(s1, "[RES_CLASS]"))
    {
      s2 = s + n;
      if (*s2 != 0)
      {
	s2++;
      }
      sscanf(s2, "%[^\n]", s1);
      matches[num_match - 1].res_class = duplicate(s1);
    }
    else if (!strcmp(s1, "[WM_NAME]"))
    {
      s2 = s + n;
      if (*s2 != 0)
      {
	s2++;
      }
      sscanf(s2, "%[^\n]", s1);
      matches[num_match - 1].wm_name = duplicate(s1);
    }
    else if (!strcmp(s1, "[WM_COMMAND]"))
    {
      sscanf(s, "%*s %i%n",
	&matches[num_match - 1].wm_command_count, &pos);
      matches[num_match - 1].wm_command =
	(char **) safemalloc (matches[num_match - 1].wm_command_count *
			      sizeof (char *));
      for (i = 0; i < matches[num_match - 1].wm_command_count; i++)
      {
        sscanf (s+pos, "%s%n", s1, &pos1);
        pos += pos1;
        matches[num_match - 1].wm_command[i] = duplicate (s1);
      }
    }
  }
  fclose(f);
}

/* This complicated logic is from twm, where it is explained */

#define xstreq(a,b) ((!a && !b) || (a && b && (strcmp(a,b)==0)))

static Bool matchWin(FvwmWindow *w, Match *m)
{
  char *client_id = NULL;
  char *window_role = NULL;
  char **wm_command = NULL;
  int wm_command_count = 0, i;
  int found;

  found = 0;
  client_id = GetClientID(w->w);

  if (xstreq(client_id, m->client_id))
  {

    /* client_id's match */

    window_role = GetWindowRole(w->w);

    if (window_role || m->window_role)
    {
      /* We have or had a window role, base decision on it */

      found = xstreq(window_role, m->window_role);
    }
    else
    {
      /* Compare res_class, res_name and WM_NAME, unless the
         user has changed WM_NAME */

      if (xstreq(w->class.res_name, m->res_name) &&
          xstreq(w->class.res_class, m->res_class) &&
          (IS_NAME_CHANGED(w) || IS_NAME_CHANGED(m) ||
           xstreq(w->name, m->wm_name)))
      {
        if (client_id)
        {
          /* If we have a client_id, we don't compare
             WM_COMMAND, since it will be different. */
          found = 1;
        }
        else
        {
          /* for non-SM-aware clients we also compare WM_COMMAND */

	  if (!XGetCommand (dpy, w->w, &wm_command, &wm_command_count))
	  {
	    wm_command = NULL;
	    wm_command_count = 0;
	  }
          if (wm_command_count == m->wm_command_count)
          {
            for (i = 0; i < wm_command_count; i++)
            {
              if (strcmp(unspace_string(wm_command[i]), m->wm_command[i]) != 0)
                break;
            }

            if (i == wm_command_count)
            {
              /* migo (21/Oct/1999): on restarts compare window ids too */
              if (!Restarting || w->w == m->win)
                found = 1;
            }
          } /* if (wm_command_count ==... */
        } /* else no client id */
      } /* if res_class, res_name and wm_name agree */
    } /* else no window roles */
  } /* if client_id's agree */

  if (client_id)
    XFree (client_id);

  if (window_role)
    XFree (window_role);

  if (wm_command)
    XFreeStringList (wm_command);

#ifdef FVWM_DEBUG_DEVEL
  fprintf(stderr, "\twin(%s, %s, %s, %d)\n[%d]\tmat(%s, %s, %s, %d)\n\n",
	  w->class.res_name, w->class.res_class, w->name, IS_NAME_CHANGED(w),
	  found,
	  m->res_name, m->res_class, m->wm_name, IS_NAME_CHANGED(m));
#endif
  return found;
}

/*
  This routine (potentially) changes the flags STARTICONIC,
  MAXIMIZED, WSHADE and STICKY and the Desk and
  attr.x, .y, .width, .height entries. It also changes the
  stack_before pointer to return information about the
  desired stacking order. It expects the stacking order
  to be set up correctly beforehand!
*/
Bool
MatchWinToSM(FvwmWindow *ewin, int *do_shade, int *do_max)
{
  int i;

  if (!does_file_version_match)
    return False;
  for (i = 0; i < num_match; i++)
  {
    if (!matches[i].used && matchWin(ewin, &matches[i]))
    {
      matches[i].used = 1;
      if (!Restarting)
      {
	/* We don't want to restore too much state if
	   we are restarting, since that would make restarting
	   useless for rereading changed rc files. */
	SET_DO_SKIP_WINDOW_LIST(
	  ewin, DO_SKIP_WINDOW_LIST(&(matches[i])));
	SET_ICON_SUPPRESSED(ewin, IS_ICON_SUPPRESSED(&(matches[i])));
	SET_HAS_NO_ICON_TITLE(ewin, HAS_NO_ICON_TITLE(&(matches[i])));
	SET_LENIENT(ewin, IS_LENIENT(&(matches[i])));
	SET_ICON_STICKY(ewin, IS_ICON_STICKY(&(matches[i])));
	SET_DO_SKIP_ICON_CIRCULATE(
	  ewin, DO_SKIP_ICON_CIRCULATE(&(matches[i])));
	SET_DO_SKIP_SHADED_CIRCULATE(
	  ewin, DO_SKIP_SHADED_CIRCULATE(&(matches[i])));
	SET_DO_SKIP_CIRCULATE(ewin, DO_SKIP_CIRCULATE(&(matches[i])));
	SET_FOCUS_MODE(ewin, GET_FOCUS_MODE(&(matches[i])));
	if (matches[i].wm_name)
	{
	  if (ewin->name)
	  {
	    free(ewin->name);
	  }
	  ewin->name = matches[i].wm_name;
	}
      }
      SET_PLACED_WB3(ewin,IS_PLACED_WB3(&(matches[i])));
      SET_PLACED_BY_FVWM(ewin,IS_PLACED_BY_FVWM(&(matches[i])));
      *do_shade = IS_SHADED(&(matches[i]));
      *do_max = IS_MAXIMIZED(&(matches[i]));
      SET_ICON_MOVED(ewin, IS_ICON_MOVED(&(matches[i])));
      if (IS_ICONIFIED(&(matches[i])))
      {
	/*
	  ICON_MOVED is necessary to make fvwm use icon_[xy]_loc
	  for icon placement
	*/
	SET_DO_START_ICONIC(ewin, 1);
	if (!IS_ICON_MOVED(ewin))
	{
	  /* only temporary to initially place the icon */
	  SET_ICON_MOVED(ewin, 1);
	  SET_DELETE_ICON_MOVED(ewin, 1);
	}
      }
      else
      {
	SET_DO_START_ICONIC(ewin, 0);
      }

      ewin->normal_g.x = matches[i].x;
      ewin->normal_g.y = matches[i].y;
      ewin->normal_g.width = matches[i].w;
      ewin->normal_g.height = matches[i].h;
      ewin->max_g.x = matches[i].x_max;
      ewin->max_g.y = matches[i].y_max;
      ewin->max_g.width = matches[i].w_max;
      ewin->max_g.height = matches[i].h_max;
      ewin->max_g_defect.width = matches[i].width_defect_max;
      ewin->max_g_defect.height = matches[i].height_defect_max;
      ewin->max_offset.x = matches[i].max_x_offset;
      ewin->max_offset.y = matches[i].max_y_offset;
      SET_STICKY(ewin, IS_STICKY(&(matches[i])));
      ewin->Desk = (IS_STICKY(ewin)) ? Scr.CurrentDesk : matches[i].desktop;
      set_layer(ewin, matches[i].layer);

      /* this is not enough to fight fvwms attempts to
	 put icons on the current page */
      ewin->icon_g.x = matches[i].icon_x;
      ewin->icon_g.y = matches[i].icon_y;
      if (!IS_STICKY(&(matches[i])) &&
	  !(IS_ICONIFIED(&(matches[i])) && IS_ICON_STICKY(&(matches[i]))))
      {
	ewin->icon_g.x -= Scr.Vx;
	ewin->icon_g.y -= Scr.Vy;
      }

      return True;
    }
  }

  return False;
}

static int saveStateFile(char *filename)
{
  FILE *f;
  int success;

  if (!filename || !*filename)
    return 0;
  if ((f = fopen(filename, "w")) == NULL)
    return 0;

  fprintf(f, "# This file is generated by fvwm."
	  " It stores global and window states.\n");
  fprintf(f, "# Normally, you must never delete this file,"
	  " it will be auto-deleted.\n\n");

#ifdef SESSION
  if (goingToRestart)
  {
    fprintf(f, "[REAL_STATE_FILENAME] %s\n", realStateFilename);
    goingToRestart = False;  /* not needed */
  }
#endif

  success = doPreserveState?
    SaveVersionInfo(f) && SaveWindowStates(f) && SaveGlobalState(f):
    1;
  doPreserveState = True;
  if (fclose(f) != 0)
    return 0;

#ifdef FVWM_DEBUG_DEVEL
  system(CatString3("mkdir -p /tmp/fs-save; cp ", filename, " /tmp/fs-save"));
#endif
#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\tSaving %s\n", filename);
#endif

  return success;
}

void
RestartInSession (char *filename, Bool isNative, Bool _doPreserveState)
{
  doPreserveState = _doPreserveState;
#ifdef SESSION
  if (sm_conn && isNative)
  {
    goingToRestart = True;

    saveStateFile(filename);
    setSmProperties(sm_conn, filename, SmRestartImmediately);

    MoveViewport(0, 0, False);
    Reborder();

    CloseICCCM2();
    XCloseDisplay(dpy);

    SmcCloseConnection(sm_conn, 0, NULL);

#ifdef FVWM_DEBUG_MSGS
    fprintf(stderr, "[FVWM]: Exiting, now SM must restart us.\n");
#endif
    /* Close all my pipes */
    ClosePipes();

    exit(0); /* let the SM restart us */
  }
#endif
  saveStateFile(filename);
  /* return and let Done restart us */
}

#ifdef SESSION
void SetClientID(char *client_id)
{
  previous_sm_client_id = client_id;
}

static void
callback_save_yourself2(SmcConn sm_conn, SmPointer client_data)
{
  Bool success = 0;
  char *filename = getUniqueStateFilename();

#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[callback_save_yourself2]\n");
#endif

  success = saveStateFile(filename);
  if (success)
  {
    setSmProperties(sm_conn, filename, SmRestartIfRunning);
    setRealStateFilename(filename);
  }
  free(filename);

  SmcSaveYourselfDone (sm_conn, success);
  sent_save_done = 1;
}

static void
setSmProperties (SmcConn sm_conn, char *filename, char hint)
{
  SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6];
  SmPropValue prop1val, prop2val, prop3val, prop4val;
/*#ifdef XSM_BUGGY_DISCARD_COMMAND*/
  SmPropValue prop6val;
/*#endif*/
  struct passwd *pwd;
  char *user_id;
  int numVals, i, priority = 30;
  Bool xsmDetected;

#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[setSmProperties] state filename: %s%s\n", filename,
    sm_conn? "": " - not connected");
#endif

  if (!sm_conn) return;

  pwd = getpwuid (getuid());
  user_id = pwd->pw_name;

  prop1.name = SmProgram;
  prop1.type = SmARRAY8;
  prop1.num_vals = 1;
  prop1.vals = &prop1val;
  prop1val.value = g_argv[0];
  prop1val.length = strlen (g_argv[0]);

  prop2.name = SmUserID;
  prop2.type = SmARRAY8;
  prop2.num_vals = 1;
  prop2.vals = &prop2val;
  prop2val.value = (SmPointer) user_id;
  prop2val.length = strlen (user_id);

  prop3.name = SmRestartStyleHint;
  prop3.type = SmCARD8;
  prop3.num_vals = 1;
  prop3.vals = &prop3val;
  prop3val.value = (SmPointer) &hint;
  prop3val.length = 1;

  prop4.name = "_GSM_Priority";
  prop4.type = SmCARD8;
  prop4.num_vals = 1;
  prop4.vals = &prop4val;
  prop4val.value = (SmPointer) &priority;
  prop4val.length = 1;

  prop5.name = SmRestartCommand;
  prop5.type = SmLISTofARRAY8;

  prop5.vals = (SmPropValue *) malloc ((g_argc + 7) * sizeof (SmPropValue));

  numVals = 0;

  for (i = 0; i < g_argc; i++)
  {
    if (strcmp (g_argv[i], "-clientId") == 0 ||
        strcmp (g_argv[i], "-restore") == 0 ||
        strcmp (g_argv[i], "-d") == 0)
    {
      i++;
    }
    else if (strcmp (g_argv[i], "-s") != 0)
    {
      prop5.vals[numVals].value = (SmPointer) g_argv[i];
      prop5.vals[numVals++].length = strlen (g_argv[i]);
    }
  }

  prop5.vals[numVals].value = (SmPointer) "-d";
  prop5.vals[numVals++].length = 2;

  prop5.vals[numVals].value = (SmPointer) XDisplayString (dpy);
  prop5.vals[numVals++].length = strlen (XDisplayString (dpy));

  prop5.vals[numVals].value = (SmPointer) "-s";
  prop5.vals[numVals++].length = 2;

  prop5.vals[numVals].value = (SmPointer) "-clientId";
  prop5.vals[numVals++].length = 9;

  prop5.vals[numVals].value = (SmPointer) sm_client_id;
  prop5.vals[numVals++].length = strlen (sm_client_id);

  prop5.vals[numVals].value = (SmPointer) "-restore";
  prop5.vals[numVals++].length = 8;

  prop5.vals[numVals].value = (SmPointer) filename;
  prop5.vals[numVals++].length = strlen (filename);

  prop5.num_vals = numVals;

  prop6.name = SmDiscardCommand;

/*
 * migo (15-Jul-1999): IMHO, XSM_BUGGY_DISCARD_COMMAND must be deleted at all.
 * Is there a better way to detect xsm, then requiring to set environment?
 */
  xsmDetected =
#ifdef XSM_BUGGY_DISCARD_COMMAND
    1
#else
    StrEquals(getenv("SESSION_MANAGER_NAME"), "xsm")
#endif
  ;

  /*#ifdef XSM_BUGGY_DISCARD_COMMAND*/
  if (xsmDetected) {
    /* the protocol spec says that the discard command
       should be LISTofARRAY8 on posix systems, but xsm
       demands that it be ARRAY8.
    */
    char *discardCommand = alloca((10 + strlen(filename)) * sizeof(char));
    sprintf (discardCommand, "rm -f '%s'", filename);
    prop6.type = SmARRAY8;
    prop6.num_vals = 1;
    prop6.vals = &prop6val;
    prop6val.value = (SmPointer) discardCommand;
    prop6val.length = strlen (discardCommand);
    /*#else*/
  } else {
    prop6.type = SmLISTofARRAY8;
    prop6.num_vals = 3;
    prop6.vals = (SmPropValue *) malloc (3 * sizeof (SmPropValue));
    prop6.vals[0].value = "rm";
    prop6.vals[0].length = 2;
    prop6.vals[1].value = "-f";
    prop6.vals[1].length = 2;
    prop6.vals[2].value = filename;
    prop6.vals[2].length = strlen (filename);
    /*#endif*/
  }

  props[0] = &prop1;
  props[1] = &prop2;
  props[2] = &prop3;
  props[3] = &prop4;
  props[4] = &prop5;
  props[5] = &prop6;

  SmcSetProperties (sm_conn, 6, props);

  free ((char *) prop5.vals);
/*#ifndef XSM_BUGGY_DISCARD_COMMAND*/
  if (!xsmDetected) free ((char *) prop6.vals);
/*#endif*/
}

/* ------------------------------------------------------------------------ */
static void
callback_save_yourself(SmcConn sm_conn, SmPointer client_data,
		       int save_style, Bool shutdown, int interact_style,
		       Bool fast)
{
#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[callback_save_yourself] (save=%d, shut=%d, intr=%d, fast=%d)\n", save_style, shutdown, interact_style, fast);
#endif

  if (!SmcRequestSaveYourselfPhase2(sm_conn, callback_save_yourself2, NULL))
  {
    SmcSaveYourselfDone (sm_conn, False);
    sent_save_done = 1;
  }
  else
    sent_save_done = 0;
}

/* ------------------------------------------------------------------------ */
static void
callback_die(SmcConn sm_conn, SmPointer client_data)
{
#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[callback_die]\n");
#endif

  SmcCloseConnection(sm_conn, 0, NULL);
  sm_fd = -1;

  if (master_pid != getpid())
    kill(master_pid, SIGTERM);
  Done(0, NULL);
}

/* ------------------------------------------------------------------------ */
static void
callback_save_complete(SmcConn sm_conn, SmPointer client_data)
{
#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[callback_save_complete]\n");
#endif
}

/* ------------------------------------------------------------------------ */
static void
callback_shutdown_cancelled(SmcConn sm_conn, SmPointer client_data)
{
#ifdef FVWM_DEBUG_MSGS
  fprintf(stderr, "\t[callback_shutdown_cancelled]\n");
#endif

  if (!sent_save_done)
  {
    SmcSaveYourselfDone(sm_conn, False);
    sent_save_done = 1;
  }
}

/* the following is taken from xsm */

static IceIOErrorHandler prev_handler;

/* ------------------------------------------------------------------------ */
static void
MyIoErrorHandler (IceConn ice_conn)
{
  if (prev_handler)
    (*prev_handler) (ice_conn);
}

/* ------------------------------------------------------------------------ */
static void
InstallIOErrorHandler (void)
{
  IceIOErrorHandler default_handler;

  prev_handler = IceSetIOErrorHandler (NULL);
  default_handler = IceSetIOErrorHandler (MyIoErrorHandler);
  if (prev_handler == default_handler)
    prev_handler = NULL;
}

/* ------------------------------------------------------------------------ */
void
SessionInit(void)
{
  char error_string_ret[4096] = "";
  static SmPointer context;
  SmcCallbacks callbacks;

  InstallIOErrorHandler();

  callbacks.save_yourself.callback = callback_save_yourself;
  callbacks.die.callback = callback_die;
  callbacks.save_complete.callback = callback_save_complete;
  callbacks.shutdown_cancelled.callback = callback_shutdown_cancelled;

  callbacks.save_yourself.client_data =
    callbacks.die.client_data =
    callbacks.save_complete.client_data =
    callbacks.shutdown_cancelled.client_data = (SmPointer) NULL;

  sm_conn = SmcOpenConnection(NULL, &context, SmProtoMajor, SmProtoMinor,
			      SmcSaveYourselfProcMask | SmcDieProcMask |
			      SmcSaveCompleteProcMask |
			      SmcShutdownCancelledProcMask,
			      &callbacks, previous_sm_client_id, &sm_client_id,
			      4096, error_string_ret);

  if (!sm_conn)
    {
      /*
	Don't annoy users which don't use a session manager
      */
      if (previous_sm_client_id)
        {
	  fvwm_msg(ERR, "SessionInit",
		   "While connecting to session manager:\n%s.",
		   error_string_ret);
        }
      sm_fd = -1;
    }
  else
    {
      sm_fd = IceConnectionNumber(SmcGetIceConnection(sm_conn));
      setInitFunctionName(0, "SessionInitFunction");
      setInitFunctionName(1, "SessionRestartFunction");
      setInitFunctionName(2, "SessionExitFunction");
    }
}

/* ------------------------------------------------------------------------ */
void
ProcessICEMsgs(void)
{
  IceProcessMessagesStatus status;

  if (sm_fd < 0)
    return;
  status = IceProcessMessages(SmcGetIceConnection(sm_conn), NULL, NULL);
  if (status == IceProcessMessagesIOError)
    {
      fvwm_msg(ERR, "ProcessICEMSGS",
	       "Connection to session manager lost\n");
      sm_conn = NULL;
      sm_fd = -1;
    }
}
#endif


/* ------------------------------------------------------------------------ */
/*
 * Fvwm Function implementation: QuitSession, SaveSession, SaveQuitSession.
 * migo (15-Jun-1999): I am not sure this is right.
 * The session mabager must implement SmsSaveYourselfRequest (xsm doesn't?).
 * Alternative implementation may use unix signals, but this does not work
 * with all session managers (also must suppose that SM runs locally).
 */

#ifdef SESSION
int getSmPid(void)
{
  const char *sessionManagerVar = getenv("SESSION_MANAGER");
  const char *smPidStrPtr;
  int smPid = 0;

  if (!sessionManagerVar) return 0;
  smPidStrPtr = strchr(sessionManagerVar, ',');
  if (!smPidStrPtr) return 0;

  while (smPidStrPtr > sessionManagerVar && isdigit(*(--smPidStrPtr)))
    ;
  while (isdigit(*(++smPidStrPtr))) {
    smPid = smPid * 10 + *smPidStrPtr - '0';
  }
  return smPid;
}
#endif

/*
 * quitSession - hopefully shutdowns the session
 */
Bool quitSession(void)
{
#ifdef SESSION
  if (!sm_conn) return False;

  SmcRequestSaveYourself(
    sm_conn,
    SmSaveLocal,
    True,               /* shutdown */
    SmInteractStyleAny,
    False,              /* fast */
    True                /* global */
  );
  return True;

/* migo: xsm does not support RequestSaveYourself, but supports signals: */
/*
  int smPid = getSmPid();
  if (!smPid) return False;
  return kill(smPid, SIGTERM) == 0? True: False;
*/
#else
  return False;
#endif
}

/*
 * saveSession - hopefully saves the session
 */
Bool saveSession(void)
{
#ifdef SESSION
  if (!sm_conn) return False;

  SmcRequestSaveYourself(
    sm_conn,
    SmSaveBoth,
    False,              /* shutdown */
    SmInteractStyleAny,
    False,              /* fast */
    True                /* global */
  );
  return True;

/* migo: xsm does not support RequestSaveYourself, but supports signals: */
/*
  int smPid = getSmPid();
  if (!smPid) return False;
  return kill(smPid, SIGUSR1) == 0? True: False;
*/
#else
  return False;
#endif
}

/*
 * saveQuitSession - hopefully saves and shutdowns the session
 */
Bool saveQuitSession(void)
{
#ifdef SESSION
  if (!sm_conn) return False;

  SmcRequestSaveYourself(
    sm_conn,
    SmSaveBoth,
    True,               /* shutdown */
    SmInteractStyleAny,
    False,              /* fast */
    True                /* global */
  );
  return True;

/* migo: xsm does not support RequestSaveYourself, but supports signals: */
/*
  if (saveSession() == False) return False;
  sleep(3);  / * doesn't work anyway * /
  if (quitSession() == False) return False;
  return True;
*/
#else
  return False;
#endif
}

void CMD_QuitSession(F_CMD_ARGS)
{
  quitSession();
}

void CMD_SaveSession(F_CMD_ARGS)
{
  saveSession();
}

void CMD_SaveQuitSession(F_CMD_ARGS)
{
  saveQuitSession();
}


syntax highlighted by Code2HTML, v. 0.9.1