Subject: Changes to Restart command

From: Mikhael Goikhman <[email-masked]>
Date: Thu, 3 Jun 1999 03:35:50 +0300 (IDT)

Ok, here is another restart patch, it is multi-filed, use: -p 1 -u.

User visible changes:
  * Implemented Restart command parsing (single/double quotes, backslash;
    no environment expansion yet). The following works as in shell:
    Restart xterm -n '"X console"' -T \"X\ console\" -e fvwm1 -s -d :0.0
  * On parse error - the native Restart (using -restore) is used.
  * When the command is failed, first try the native Restart and then
    try to start fvwm only with initial arguments (which must work).
  * -s flag is not added on foreign Restart (use "Restart fvwm1 -s").

Note, if the executable name is not fvwm2, "Restart fvwm2" now works as a
foreign restart, which is correct, I believe. One must always use "Restart"
without parameters for a native restart if he is not sure about the
executable name. "Restart this-executable-name" is left to work as a native
restart (with fvwm parameters added) only for backward compatibility.
A patched man page includes this info.

Regards,
Mikhael.
Implements Restart command parsing and more.

Apply this patch in the directory of ./NEWS as:
  patch -p 1 -u <restart.patch

diff -u -r fvwm-snap-19990602/ChangeLog fvwm-snap-changed/ChangeLog
--- fvwm-snap-19990602/ChangeLog	Wed Jun  2 14:30:06 1999
+++ fvwm-snap-changed/ChangeLog	Thu Jun  3 01:19:34 1999
@@ -1,3 +1,16 @@
+1999-06-02  Mikhael Goikhman  <[email-masked]>
+
+	* fvwm/fvwm.c (Done, parseCommandArgs):
+	Implemented and used function parseCommandArgs(), accept shell-like
+	syntax in the Restart command.
+
+	* fvwm/fvwm.c (Done, getBasename): 
+	Implemented and used function getBasename(). If fvwm argv[0] is
+	[path1/]the_executable, the two forms "Restart [path2/]the_executable"
+	and "Restart" work as a native restart (using session or -restore -s).
+	Any other form of the Restart command processed as a foreign restart
+	(with command parsing). The argument list is limited to 30.
+
 1999-06-02  Matthias Clasen  <[email-masked]>
 
 	* fvwm/session.c (LoadGlobalState): Make restarting return to

diff -u -r fvwm-snap-19990602/NEWS fvwm-snap-changed/NEWS
--- fvwm-snap-19990602/NEWS	Sun May 30 14:30:07 1999
+++ fvwm-snap-changed/NEWS	Thu Jun  3 01:09:23 1999
@@ -1,5 +1,7 @@
 Changes in alpha release 2.3.3 (May 1999)
 
+* The Restart command accepts simple shell-like syntax now.
+
 * Colour gradients may use up to 1000 colours. The old limit was 128 colours.
 
 * New command MaxWindowSize limits the initial dimensions of a window.

diff -u -r fvwm-snap-19990602/fvwm/fvwm.c fvwm-snap-changed/fvwm/fvwm.c
--- fvwm-snap-19990602/fvwm/fvwm.c	Mon May 31 14:30:20 1999
+++ fvwm-snap-changed/fvwm/fvwm.c	Thu Jun  3 00:58:49 1999
@@ -98,6 +98,9 @@
 void SetRCDefaults(void);
 void StartupStuff(void);
 
+const char *getBasename(const char *filename);
+int parseCommandArgs(const char *cmd, char **argv, int maxv, const char **err);
+
 XContext FvwmContext;		/* context for fvwm windows */
 XContext MenuContext;		/* context for fvwm menus */
 
@@ -1718,67 +1721,74 @@
 
   if(restart)
   {
-     char* filename = strdup( CatString2(user_home_dir, "/.fvwm_restart") );
+    char* filename = strdup( CatString2(user_home_dir, "/.fvwm_restart") );
+    int native = 1;  /* native restart of this executable */
+
+    if (command == NULL || command[0] == '\0') command = g_argv[0];
+    if (strcmp(getBasename(command), getBasename(g_argv[0])) != 0) native = 0;
 
-     if (command == NULL || command[0] == '\0') command = g_argv[0];
-     if (*command == '\0' || strstr (command, "fvwm2"))
-       {
-	 RestartInSession (filename); /* won't return under SM */
-       }
+    if (native) {
+      RestartInSession (filename); /* won't return under SM */
+    }
 
     /* Really make sure that the connection is closed and cleared! */
     XSelectInput(dpy, Scr.Root, 0 );
     XSync(dpy, 0);
     XCloseDisplay(dpy);
 
+    /* really need to destroy all windows, explicitly,
+     * not sleep, but this is adequate for now */
+    sleep(1);
+    ReapChildren();
+
     {
-      char *my_argv[20];
+      const int MAX_ARG_SIZE = 30;
+      char *my_argv[MAX_ARG_SIZE];
       int i,done,j;
 
+      if (!native) {
+        const char *errorMsg;
+        i = parseCommandArgs(command, my_argv, MAX_ARG_SIZE, &errorMsg);
+        if (i <= 0) {
+          fvwm_msg(ERR, "Done", "Restart parsing error in (%s): [%s]",
+            command, errorMsg);
+        } else {
+          command = my_argv[0];
+          execvp(command,my_argv);
+          fvwm_msg(ERR, "Done", "Call of '%s' failed!!!! (restarting '%s' instead)",
+            command, g_argv[0]);
+          perror("  system error description");
+        }
+      }
+
+      /* Trying a native restart */
       i=0;
       j=0;
       done = 0;
-      if (strstr(command, "fvwm2") != NULL) {
-        /* must be 4 less than the size of my_argv to add 3 args and NULL */
-        while((g_argv[j] != NULL)&&(i<16))
+      while((g_argv[j] != NULL) && (i < MAX_ARG_SIZE - 4))
+      {
+        if(strcmp(g_argv[j],"-s")!=0)
         {
-          if(strcmp(g_argv[j],"-s")!=0)
-          {
-            my_argv[i] = g_argv[j];
-            i++;
-            j++;
-          }
-          else
-            j++;
+          my_argv[i] = g_argv[j];
+          i++;
+          j++;
         }
-        my_argv[i++] = "-s";
+        else
+          j++;
+      }
+      my_argv[i++] = "-s";
 
-        for (j = i - 1; j >= 0; j--)
-        {
-          if (strcmp (my_argv[j], "-restore") == 0)
-            break;
-        }
-        if (j >= 0) {
-          my_argv[j + 1] = filename;
-        } else {
-          my_argv[i++] = "-restore";
-          my_argv[i++] = filename;
-        }
+      for (j = i - 1; j >= 0; j--) {
+        if (strcmp (my_argv[j], "-restore") == 0)
+          break;
+      }
+      if (j >= 0) {
+        my_argv[j + 1] = filename;
       } else {
-        /* This must be divided to args by spaces! */
-        my_argv[i++] = command;
+        my_argv[i++] = "-restore";
+        my_argv[i++] = filename;
       }
-      while(i<10)
-        my_argv[i++] = NULL;
-
-      /* really need to destroy all windows, explicitly,
-       * not sleep, but this is adequate for now */
-      sleep(1);
-      ReapChildren();
-      execvp(command,my_argv);
-      fvwm_msg(ERR,"Done","Call of '%s' failed!!!! (restarting '%s' instead)",
-	       command, g_argv[0]);
-      perror("  system error description");
+      my_argv[i++] = NULL;
 
       execvp(g_argv[0], my_argv);    /* that _should_ work */
       fvwm_msg(ERR,"Done","Call of '%s' failed!!!! (trying again)", g_argv[0]);
@@ -1909,3 +1919,97 @@
     BlackoutWin = None;
   }
 } /* UnBlackoutScreen */
+
+
+/*
+ * getBasename - similar to basename(1).
+ */
+const char *getBasename(const char *filename) {
+  const char *fptr = filename + strlen(filename);
+  const char sep = '/';
+  while (fptr > filename && *(fptr-1) != sep) fptr--;
+  return fptr;
+}
+
+/*
+ * parseCommandArgs - parses a given command string into a given limited
+ * argument array suitable for execv*. The parsing is similar to shell's.
+ * Returns:
+ *   positive number of parsed arguments - on success,
+ *   0 - on empty command (only spaces),
+ *   negative - on no command or parsing error.
+ * 
+ * Any character can be quoted with a backslash (even inside single quotes).
+ * Every command argument is separated by a space/tab/new-line from both sizes
+ * or is at the start/end of a string. Sequential spaces are ignored.
+ * An argument can be enclosed into single quotes (no further expanding)
+ * or double quotes (expending environment $VAR or ${VAR}).
+ *
+ * In the current implementation, parsed arguments are stored in one
+ * large static string pointed by returned argv[0], so they will be lost
+ * on the next function call. This can be changed using dynamic allocation,
+ * in this case the caller must free the string pointed by argv[0].
+ */
+int parseCommandArgs(const char *command, char **argv, int maxArgc, const char **errorMsg) {
+  /* It is impossible to guess the exact length because of expanding */
+  #define MAX_TOTAL_ARG_LEN 256
+  /* char *argString = safemalloc(MAX_TOTAL_ARG_LEN); */
+  static char argString[MAX_TOTAL_ARG_LEN];
+  int totalArgLen = 0;
+  int errorCode = 0;
+  int argc;
+  char *aptr = argString;
+  const char *cptr = command;
+  #define theChar (*cptr)
+  #define advChar (*(cptr++))
+  #define topChar (*cptr     == '\\'? *(cptr+1): *cptr)
+  #define popChar (*(cptr++) == '\\'? *(cptr++): *(cptr-1))
+  #define isSpace (theChar == ' ' || theChar == '\t' || theChar == '\n')
+  #define canAddArgChar (totalArgLen < MAX_TOTAL_ARG_LEN-1)
+  #define addArgChar(ch) (++totalArgLen, *(aptr++) = ch)
+
+  *errorMsg = "";
+  if (!command) { *errorMsg = "No command"; return -1; }
+  for (argc = 0; argc < maxArgc; argc++) {
+    int sQuote = 0;
+    argv[argc] = aptr;
+    while (isSpace) advChar;
+    if (theChar == '\0') break;
+    while ((sQuote || !isSpace) && theChar != '\0' && canAddArgChar) {
+      if (theChar == '"') {
+        if (sQuote) { sQuote = 0; }
+        else { sQuote = 1; }
+        advChar;
+      } else if (!sQuote && theChar == '\'') {
+        advChar;
+        while (theChar != '\'' && theChar != '\0' && canAddArgChar) {
+          addArgChar(popChar);
+        }
+        if (theChar == '\'') advChar;
+        else if (!canAddArgChar) break;
+        else { *errorMsg = "No closing single quote"; errorCode = -3; break; }
+      } else if (theChar == '$') {
+        /* Not implemented yet */
+        addArgChar(advChar);
+      } else {
+        if (addArgChar(popChar) == '\0') break;
+      }
+    }
+    if (*(aptr-1) == '\0') { *errorMsg = "Unexpected last backslash"; errorCode = -2; break; }
+    *(aptr++) = '\0';
+    if (errorCode) break;
+    if (!canAddArgChar) { *errorMsg = "The command is too long"; errorCode = -argc - 100; break; }
+    if (sQuote) { *errorMsg = "No closing double quote"; errorCode = -4; break; }
+  }
+
+  #undef theChar
+  #undef advChar
+  #undef topChar
+  #undef popChar
+  #undef isSpace
+  #undef canAddArgChar
+  #undef addArgChar
+  argv[argc] = 0;
+  if (argc == 0) *errorMsg = "Void command";
+  return errorCode? errorCode: argc;
+}

diff -u -r fvwm-snap-19990602/fvwm/fvwm2.1 fvwm-snap-changed/fvwm/fvwm2.1
--- fvwm-snap-19990602/fvwm/fvwm2.1	Sun May 30 14:30:21 1999
+++ fvwm-snap-changed/fvwm/fvwm2.1	Thu Jun  3 01:56:45 1999
@@ -2472,8 +2472,8 @@
 AddToMenu Quit-Verify
  + "Really Quit Fvwm?" Title
  + "Yes, Really Quit"  Quit
- + "Restart Fvwm2"     Restart fvwm2
- + "Restart Fvwm 1.xx" Restart fvwm
+ + "Restart Fvwm2"     Restart
+ + "Restart Fvwm 1.xx" Restart fvwm -s
  + ""                  Nop
  + "No, Don't Quit"    Nop
 
@@ -2585,7 +2585,7 @@
 each coordinate, in which case the location is specified in pixels.

-.IP "Restart [ \fIWindowManagerName\fP ]"
+.IP "Restart [ \fIWindowManagerName\fP [ \fIparams\fP ] ]"
 Causes \fIfvwm2\fP to restart itself if \fIWindowManagerName\fP is
 "fvwm2" or left blank, or to switch to an alternate window manager
 if \fIWindowManagerName\fP is other than "fvwm2".  If the window
@@ -2593,17 +2593,24 @@
 full path name for \fIWindowManagerName\fP.
 
 This command should not have a trailing ampersand or any command line
-arguments and should not make use of any environmental variables.  Of
-the following examples, the first two are sure losers, but the third
-is OK:
+arguments and (currently) should not make use of any environmental variables.
+The command can have optional parameters with simple shell-like syntax.
+Of the following examples, the first two are sure losers, but the next three
+are OK:
 .EX
 Key F1 R N Restart fvwm2 &
 Key F1 R N Restart $(HOME)/bin/fvwm2
 Key F1 R N Restart /home/nation/bin/fvwm2
+Key F1 R N Restart fvwm1 -s -f .fvwm1rc
+Key F1 R N Restart xterm -n '"X console"' -T \"X\ console\" -e fvwm1 -s
 .EE
 
 The command 'Restart fvwm2' preserves most per-window state by writing
 it to a file named .fvwm_restart in the user's home directory.
+
+Note, the said above supposes your executable name is "fvwm2", if your
+executable is, for instance, "my_wm", substitute all "fvwm2" tokens above
+with "my_wm".
 
 .IP "Scroll \fIhorizonal vertical\fP"
 Scrolls the virtual desktop's viewport by \fIhorizontal\fP pages in