Browse Source

GUI overhaul.

We now have a much less sucky GUI with tabs, combo boxes and all sorts
of whizzbang stuff.

The new GUI allows setting most of the advanced features we support
directly from the service installer without having to poke about in the
registry.
Iain Patterson 8 years ago
parent
commit
b3a4446ab3
10 changed files with 620 additions and 94 deletions
  1. 4 0
      ChangeLog.txt
  2. 3 0
      README.txt
  3. 248 28
      gui.cpp
  4. 113 9
      messages.mc
  5. 7 0
      nssm.h
  6. 195 44
      nssm.rc
  7. 19 8
      registry.cpp
  8. 26 3
      resource.h
  9. 1 2
      service.cpp
  10. 4 0
      service.h

+ 4 - 0
ChangeLog.txt

@@ -1,3 +1,7 @@
+Changes since 2.18
+-----------------
+  * The GUI is significantly less sucky.
+
 Changes since 2.17
 -----------------
   * Timeouts for each shutdown method can be configured in

+ 3 - 0
README.txt

@@ -46,6 +46,9 @@ to an arbitrary path.
 Since version 2.18, NSSM can be configured to wait a user-specified amount
 of time for the application to exit when shutting down.
 
+Since version 2.19, many more service options can be configured with the
+GUI installer as well as via the registry.
+
 
 Usage
 -----

+ 248 - 28
gui.cpp

@@ -1,5 +1,9 @@
 #include "nssm.h"
 
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_NUM_TABS };
+static HWND tablist[NSSM_NUM_TABS];
+static int selected_tab;
+
 static void strip_basename(char *buffer) {
   size_t len = strlen(buffer);
   size_t i;
@@ -68,7 +72,25 @@ void centre_window(HWND window) {
   MoveWindow(window, x, y, size.right - size.left, size.bottom - size.top, 0);
 }
 
-/* Install the service */
+static inline void check_stop_method(nssm_service_t *service, unsigned long method, unsigned long control) {
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) return;
+  service->stop_method &= ~method;
+}
+
+static inline void check_method_timeout(HWND tab, unsigned long control, unsigned long *timeout) {
+  BOOL translated;
+  unsigned long configured = GetDlgItemInt(tab, control, &translated, 0);
+  if (translated) *timeout = configured;
+}
+
+static inline void check_io(char *name, char *buffer, size_t bufsize, unsigned long control) {
+  if (! SendMessage(GetDlgItem(tablist[NSSM_TAB_IO], control), WM_GETTEXTLENGTH, 0, 0)) return;
+  if (GetDlgItemText(tablist[NSSM_TAB_IO], control, buffer, (int) bufsize)) return;
+  popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, name);
+  ZeroMemory(buffer, bufsize);
+}
+
+/* Install the service. */
 int install(HWND window) {
   if (! window) return 1;
 
@@ -82,21 +104,45 @@ int install(HWND window) {
     }
 
     /* Get executable name */
-    if (! GetDlgItemText(window, IDC_PATH, service->exe, sizeof(service->exe))) {
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe, sizeof(service->exe))) {
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);
       return 3;
     }
-  
-    /* Get flags */
-    if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {
+
+    /* Get startup directory. */
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_DIR, service->dir, sizeof(service->dir))) {
+      memmove(service->dir, service->exe, sizeof(service->dir));
+      strip_basename(service->dir);
+    }
+
+    /* Get flags. */
+    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {
       if (! GetDlgItemText(window, IDC_FLAGS, service->flags, sizeof(service->flags))) {
         popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);
         return 4;
       }
     }
 
-    memmove(service->dir, service->exe, strlen(service->exe));
-    strip_basename(service->dir);
+    /* Get stop method stuff. */
+    service->stop_method = ~0;
+    check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);
+    check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);
+    check_stop_method(service, NSSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS);
+    check_stop_method(service, NSSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE);
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);
+
+    /* Get exit action stuff. */
+    check_method_timeout(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);
+    HWND combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
+    service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
+    if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;
+
+    /* Get I/O stuff. */
+    check_io("stdin", service->stdin_path, sizeof(service->stdin_path), IDC_STDIN);
+    check_io("stdout", service->stdout_path, sizeof(service->stdout_path), IDC_STDOUT);
+    check_io("stderr", service->stderr_path, sizeof(service->stderr_path), IDC_STDERR);
   }
 
   /* See if it works. */
@@ -185,12 +231,33 @@ int remove(HWND window) {
   return 0;
 }
 
+static char *browse_filter(int message) {
+  switch (message) {
+    case NSSM_GUI_BROWSE_FILTER_APPLICATIONS: return "*.exe;*.bat;*.cmd";
+    case NSSM_GUI_BROWSE_FILTER_DIRECTORIES: return ".";
+    case NSSM_GUI_BROWSE_FILTER_ALL_FILES: /* Fall through. */
+    default: return "*.*";
+  }
+}
+
+UINT_PTR CALLBACK browse_hook(HWND dlg, UINT message, WPARAM w, LPARAM l) {
+  switch (message) {
+    case WM_INITDIALOG:
+      return 1;
+  }
+
+  return 0;
+}
+
 /* Browse for application */
-void browse(HWND window) {
+void browse(HWND window, char *current, unsigned long flags, ...) {
   if (! window) return;
 
+  va_list arg;
   size_t bufsize = 256;
   size_t len = bufsize;
+  int i;
+
   OPENFILENAME ofn;
   ZeroMemory(&ofn, sizeof(ofn));
   ofn.lStructSize = sizeof(ofn);
@@ -198,28 +265,34 @@ void browse(HWND window) {
   /* XXX: Escaping nulls with FormatMessage is tricky */
   if (ofn.lpstrFilter) {
     ZeroMemory((void *) ofn.lpstrFilter, bufsize);
-    char *localised = message_string(NSSM_GUI_BROWSE_FILTER_APPLICATIONS);
-    _snprintf_s((char *) ofn.lpstrFilter, bufsize, _TRUNCATE, localised);
+    len = 0;
     /* "Applications" + NULL + "*.exe" + NULL */
-    len = strlen(localised) + 1;
-    LocalFree(localised);
-    _snprintf_s((char *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, "*.exe");
-    /* "All files" + NULL + "*.*" + NULL */
-    len += 6;
-    localised = message_string(NSSM_GUI_BROWSE_FILTER_ALL_FILES);
-    _snprintf_s((char *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, localised);
-    len += strlen(localised) + 1;
-    LocalFree(localised);
-    _snprintf_s((char *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, "*.*");
+    va_start(arg, flags);
+    while (i = va_arg(arg, int)) {
+      char *localised = message_string(i);
+      _snprintf_s((char *) ofn.lpstrFilter + len, bufsize, _TRUNCATE, localised);
+      len += strlen(localised) + 1;
+      LocalFree(localised);
+      char *filter = browse_filter(i);
+      _snprintf_s((char *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, "%s", filter);
+      len += strlen(filter) + 1;
+    }
+    va_end(arg);
     /* Remainder of the buffer is already zeroed */
   }
   ofn.lpstrFile = new char[MAX_PATH];
-  ofn.lpstrFile[0] = '\0';
+  if (flags & OFN_NOVALIDATE) {
+    /* Directory hack. */
+    _snprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, ":%s:", message_string(NSSM_GUI_BROWSE_FILTER_DIRECTORIES));
+  }
+  else _snprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, "%s", current);
   ofn.lpstrTitle = message_string(NSSM_GUI_BROWSE_TITLE);
   ofn.nMaxFile = MAX_PATH;
-  ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
+  ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | flags;
 
   if (GetOpenFileName(&ofn)) {
+    /* Directory hack. */
+    if (flags & OFN_NOVALIDATE) strip_basename(ofn.lpstrFile);
     SendMessage(window, WM_SETTEXT, 0, (LPARAM) ofn.lpstrFile);
   }
   if (ofn.lpstrFilter) HeapFree(GetProcessHeap(), 0, (void *) ofn.lpstrFilter);
@@ -227,18 +300,170 @@ void browse(HWND window) {
   delete[] ofn.lpstrFile;
 }
 
+INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {
+  switch (message) {
+    case WM_INITDIALOG:
+      return 1;
+
+    /* Button was pressed or control was controlled. */
+    case WM_COMMAND:
+      HWND dlg;
+      char buffer[MAX_PATH];
+
+      switch (LOWORD(w)) {
+        /* Browse for application. */
+        case IDC_BROWSE:
+          dlg = GetDlgItem(tab, IDC_PATH);
+          GetDlgItemText(tab, IDC_PATH, buffer, sizeof(buffer));
+          browse(dlg, buffer, OFN_FILEMUSTEXIST, NSSM_GUI_BROWSE_FILTER_APPLICATIONS, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);
+          /* Fill in startup directory if it wasn't already specified. */
+          GetDlgItemText(tab, IDC_DIR, buffer, sizeof(buffer));
+          if (! buffer[0]) {
+            GetDlgItemText(tab, IDC_PATH, buffer, sizeof(buffer));
+            strip_basename(buffer);
+            SetDlgItemText(tab, IDC_DIR, buffer);
+          }
+          break;
+
+          /* Browse for startup directory. */
+        case IDC_BROWSE_DIR:
+          dlg = GetDlgItem(tab, IDC_DIR);
+          GetDlgItemText(tab, IDC_DIR, buffer, sizeof(buffer));
+          browse(dlg, buffer, OFN_NOVALIDATE, NSSM_GUI_BROWSE_FILTER_DIRECTORIES, 0);
+          break;
+
+        /* Browse for stdin. */
+        case IDC_BROWSE_STDIN:
+          dlg = GetDlgItem(tab, IDC_STDIN);
+          GetDlgItemText(tab, IDC_STDIN, buffer, sizeof(buffer));
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);
+          break;
+
+        /* Browse for stdout. */
+        case IDC_BROWSE_STDOUT:
+          dlg = GetDlgItem(tab, IDC_STDOUT);
+          GetDlgItemText(tab, IDC_STDOUT, buffer, sizeof(buffer));
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);
+          /* Fill in stderr if it wasn't already specified. */
+          GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer));
+          if (! buffer[0]) {
+            GetDlgItemText(tab, IDC_STDOUT, buffer, sizeof(buffer));
+            SetDlgItemText(tab, IDC_STDERR, buffer);
+          }
+          break;
+
+        /* Browse for stderr. */
+        case IDC_BROWSE_STDERR:
+          dlg = GetDlgItem(tab, IDC_STDERR);
+          GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer));
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);
+          break;
+      }
+      return 1;
+  }
+
+  return 0;
+}
+
 /* Install/remove dialogue callback */
 INT_PTR CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
   switch (message) {
     /* Creating the dialogue */
     case WM_INITDIALOG:
+      HWND tabs;
+      HWND combo;
+      tabs = GetDlgItem(window, IDC_TAB1);
+      if (! tabs) return 0;
+
+      /* Set up tabs. */
+      TCITEM tab;
+      ZeroMemory(&tab, sizeof(tab));
+      tab.mask = TCIF_TEXT;
+
+      /* Application tab. */
+      tab.pszText = message_string(NSSM_GUI_TAB_APPLICATION);
+      tab.cchTextMax = (int) strlen(tab.pszText);
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_APPLICATION, (LPARAM) &tab);
+      tablist[NSSM_TAB_APPLICATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg);
+      ShowWindow(tablist[NSSM_TAB_APPLICATION], SW_SHOW);
+
+      /* Shutdown tab. */
+      tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);
+      tab.cchTextMax = (int) strlen(tab.pszText);
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_SHUTDOWN, (LPARAM) &tab);
+      tablist[NSSM_TAB_SHUTDOWN] = CreateDialog(0, MAKEINTRESOURCE(IDD_SHUTDOWN), window, tab_dlg);
+      ShowWindow(tablist[NSSM_TAB_SHUTDOWN], SW_HIDE);
+
+      /* Set defaults. */
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_CHECKED, 0);
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, NSSM_KILL_CONSOLE_GRACE_PERIOD, 0);
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_CHECKED, 0);
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, NSSM_KILL_WINDOW_GRACE_PERIOD, 0);
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_CHECKED, 0);
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, NSSM_KILL_THREADS_GRACE_PERIOD, 0);
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_CHECKED, 0);
+
+      /* Restart tab. */
+      tab.pszText = message_string(NSSM_GUI_TAB_EXIT);
+      tab.cchTextMax = (int) strlen(tab.pszText);
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_EXIT, (LPARAM) &tab);
+      tablist[NSSM_TAB_EXIT] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPEXIT), window, tab_dlg);
+      ShowWindow(tablist[NSSM_TAB_EXIT], SW_HIDE);
+
+      /* Set defaults. */
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, NSSM_RESET_THROTTLE_RESTART, 0);
+      combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_RESTART, (LPARAM) message_string(NSSM_GUI_EXIT_RESTART));
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_IGNORE, (LPARAM) message_string(NSSM_GUI_EXIT_IGNORE));
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_REALLY, (LPARAM) message_string(NSSM_GUI_EXIT_REALLY));
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_UNCLEAN, (LPARAM) message_string(NSSM_GUI_EXIT_UNCLEAN));
+      SendMessage(combo, CB_SETCURSEL, NSSM_EXIT_RESTART, 0);
+
+      /* I/O tab. */
+      tab.pszText = message_string(NSSM_GUI_TAB_IO);
+      tab.cchTextMax = (int) strlen(tab.pszText) + 1;
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_IO, (LPARAM) &tab);
+      tablist[NSSM_TAB_IO] = CreateDialog(0, MAKEINTRESOURCE(IDD_IO), window, tab_dlg);
+      ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE);
+
+      selected_tab = 0;
+
       return 1;
 
+    /* Tab change. */
+    case WM_NOTIFY:
+      NMHDR *notification;
+
+      notification = (NMHDR *) l;
+      switch (notification->code) {
+        case TCN_SELCHANGE:
+          HWND tabs;
+          int selection;
+
+          tabs = GetDlgItem(window, IDC_TAB1);
+          if (! tabs) return 0;
+
+          selection = (int) SendMessage(tabs, TCM_GETCURSEL, 0, 0);
+          if (selection != selected_tab) {
+            ShowWindow(tablist[selected_tab], SW_HIDE);
+            /*
+              XXX: Sets focus to the service name which isn't ideal but is
+                   better than leaving it in another tab.
+            */
+            ShowWindow(tablist[selection], SW_SHOWDEFAULT);
+            SetFocus(tablist[selection]);
+            selected_tab = selection;
+          }
+          return 1;
+      }
+
+      return 0;
+
     /* Button was pressed or control was controlled */
     case WM_COMMAND:
       switch (LOWORD(w)) {
         /* OK button */
-        case IDC_OK:
+        case IDOK:
           if (! install(window)) PostQuitMessage(0);
           break;
 
@@ -247,11 +472,6 @@ INT_PTR CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
           DestroyWindow(window);
           break;
 
-        /* Browse button */
-        case IDC_BROWSE:
-          browse(GetDlgItem(window, IDC_PATH));
-          break;
-
         /* Remove button */
         case IDC_REMOVE:
           if (! remove(window)) PostQuitMessage(0);

+ 113 - 9
messages.mc

@@ -382,29 +382,29 @@ automatico, riavviare il computer e tentare di nuovo la rimozione.
 .
 
 MessageId = +1
-SymbolicName = NSSM_GUI_BROWSE_FILTER
+SymbolicName = NSSM_GUI_BROWSE_FILTER_APPLICATIONS
 Severity = Informational
 Language = English
-Applications%sAll files%s%0
+Applications%0
 .
 Language = French
-Applications%sTous les fichiers%s%0
+Applications%0
 .
 Language = Italian
-Applicazioni%sTutti i files%s%0
+Applicazioni%0
 .
 
 MessageId = +1
-SymbolicName = NSSM_GUI_BROWSE_FILTER_APPLICATIONS
+SymbolicName = NSSM_GUI_BROWSE_FILTER_DIRECTORIES
 Severity = Informational
 Language = English
-Applications%0
+Directories%0
 .
 Language = French
-Applications%0
+Répertoires%0
 .
 Language = Italian
-Applicazioni%0
+Cartelle%0
 .
 
 MessageId = +1
@@ -433,6 +433,110 @@ Language = Italian
 Ricerca file applicazione
 .
 
+MessageId = +1
+SymbolicName = NSSM_GUI_TAB_APPLICATION
+Severity = Informational
+Language = English
+Application
+.
+Language = French
+Application
+.
+Language = Italian
+Applicazione
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_TAB_SHUTDOWN
+Severity = Informational
+Language = English
+Shutdown
+.
+Language = French
+Shutdown
+.
+Language = Italian
+Shutdown
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_TAB_EXIT
+Severity = Informational
+Language = English
+Exit actions
+.
+Language = French
+Exit actions
+.
+Language = Italian
+Exit actions
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_TAB_IO
+Severity = Informational
+Language = English
+I/O
+.
+Language = French
+I/O
+.
+Language = Italian
+I/O
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_EXIT_RESTART
+Severity = Informational
+Language = English
+Restart application
+.
+Language = French
+Redémarrer l'application
+.
+Language = Italian
+Riavvare l'applicazione
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_EXIT_IGNORE
+Severity = Informational
+Language = English
+No action (srvany compatible)
+.
+Language = French
+No action (srvany compatible)
+.
+Language = Italian
+No action (srvany compatible)
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_EXIT_REALLY
+Severity = Informational
+Language = English
+Stop service (oneshot mode)
+.
+Language = French
+Stop service (oneshot mode)
+.
+Language = Italian
+Stop service (oneshot mode)
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_EXIT_UNCLEAN
+Severity = Informational
+Language = English
+Fake crash (pre-Vista)
+.
+Language = French
+Fake crash (pre-Vista)
+.
+Language = Italian
+Fake crash (pre-Vista)
+.
+
 MessageId = 1001
 SymbolicName = NSSM_EVENT_DISPATCHER_FAILED
 Severity = Error
@@ -1041,7 +1145,7 @@ Language = French
 La déclaration de l'environnement %1 pour le service %2 n'est pas du type REG_MULTI_SZ.  Cette déclaration sera ignorée.
 .
 Language = Italian
-Dichiarazione di ambiente %1 per il servizio %2 non è di tipo REG_MULTI_SZ e verrà quindi ingnorata.
+Dichiarazione di ambiente %1 per il servizio %2 non è di tipo REG_MULTI_SZ e verrà quindi ignorata.
 .
 
 MessageId = +1

+ 7 - 0
nssm.h

@@ -53,6 +53,13 @@ int str_equiv(const char *, const char *);
 #define NSSM_STOP_METHOD_THREADS (1 << 2)
 #define NSSM_STOP_METHOD_TERMINATE (1 << 3)
 
+/* Exit actions. */
+#define NSSM_EXIT_RESTART 0
+#define NSSM_EXIT_IGNORE 1
+#define NSSM_EXIT_REALLY 2
+#define NSSM_EXIT_UNCLEAN 3
+#define NSSM_NUM_EXIT_ACTIONS 4
+
 /* How many milliseconds to wait before updating service status. */
 #define NSSM_SERVICE_STATUS_DEADLINE 20000
 

+ 195 - 44
nssm.rc

@@ -63,22 +63,18 @@ IDI_NSSM                ICON                    "nssm.ico"
 // Dialog
 //
 
-IDD_INSTALL DIALOG  0, 0, 220, 90
-STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "NSSM service installer"
 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+IDD_INSTALL DIALOG 0, 0, 286, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "NSSM service installer"
 FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "Install service",IDC_OK,55,69,50,14
-    PUSHBUTTON      "Cancel",IDCANCEL,111,69,50,14
-    EDITTEXT        IDC_PATH,48,7,110,14,ES_AUTOHSCROLL
-    PUSHBUTTON      "Browse",IDC_BROWSE,163,7,50,14
-    EDITTEXT        IDC_FLAGS,48,28,165,14,ES_AUTOHSCROLL
-    LTEXT           "Options:",IDC_STATIC,7,31,27,8
-    LTEXT           "Service\nname:",IDC_STATIC,7,49,41,18
-    EDITTEXT        IDC_NAME,48,49,77,14,ES_AUTOHSCROLL
-    LTEXT           "Application:",IDC_STATIC,7,9,38,8
-END
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Service name:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Install service", IDOK, 172, 104, 50, 14
+    PUSHBUTTON      "Cancel", IDCANCEL, 227, 104, 50, 14
+}
 
 IDD_REMOVE DIALOG  0, 0, 223, 28
 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
@@ -90,6 +86,76 @@ BEGIN
     EDITTEXT        IDC_NAME,59,7,87,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+IDD_APPLICATION DIALOG 9, 20, 261, 73
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Application", IDC_STATIC, 7, 7, 251, 58
+    LTEXT           "Path:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 70, 16, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE, 239, 15, 15, 14
+    LTEXT           "Startup directory:", IDC_STATIC, 13, 34, 53, 8, SS_LEFT
+    EDITTEXT        IDC_DIR, 70, 32, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_DIR, 239, 31, 15, 14
+    LTEXT           "Options:", IDC_STATIC, 13, 50, 53, 8, SS_LEFT
+    EDITTEXT        IDC_FLAGS, 70, 48, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
+
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+IDD_SHUTDOWN DIALOG 9, 20, 261, 75
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Shutdown", IDC_STATIC, 7, 7, 251, 68
+    AUTOCHECKBOX    "Generate Control-C", IDC_METHOD_CONSOLE, 13, 18, 76, 8
+    LTEXT           "Timeout:", IDC_STATIC, 135, 18, 26, 8, SS_LEFT
+    EDITTEXT        IDC_KILL_CONSOLE, 163, 16, 29, 12, ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "ms", IDC_STATIC, 194, 18, 10, 8, SS_LEFT
+    AUTOCHECKBOX    "Send WM_CLOSE to windows", IDC_METHOD_WINDOW, 13, 32, 113, 8
+    LTEXT           "Timeout:", IDC_STATIC, 135, 32, 26, 8, SS_LEFT
+    EDITTEXT        IDC_KILL_WINDOW, 163, 30, 29, 12, ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "ms", IDC_STATIC, 194, 32, 10, 8, SS_LEFT
+    AUTOCHECKBOX    "Post WM_QUIT to threads", IDC_METHOD_THREADS, 13, 46, 100, 8
+    EDITTEXT        IDC_KILL_THREADS, 163, 44, 29, 12, ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "ms", IDC_STATIC, 194, 46, 10, 8, SS_LEFT
+    LTEXT           "Timeout:", IDC_STATIC, 135, 46, 26, 8, SS_LEFT
+    AUTOCHECKBOX    "Terminate process", IDC_METHOD_TERMINATE, 13, 60, 74, 8
+}
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+IDD_APPEXIT DIALOG 9, 20, 261, 73
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Throttling", IDC_STATIC, 7, 7, 251, 25
+    LTEXT           "Delay restart if application runs for less than", IDC_STATIC, 13, 18, 137, 8, SS_LEFT
+    EDITTEXT        IDC_THROTTLE, 152, 16, 29, 12, ES_AUTOHSCROLL | ES_NUMBER
+    LTEXT           "ms", IDC_STATIC, 186, 18, 10, 8, SS_LEFT
+    GROUPBOX        "Restart", IDC_STATIC, 7, 33, 251, 35
+    LTEXT           "Action to take when application exits other\nthan in response to a controlled service\nshutdown:", IDC_STATIC, 14, 42, 134, 24, SS_LEFT
+    COMBOBOX        IDC_APPEXIT, 153, 47, 100, 120, CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_TABSTOP
+}
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+IDD_IO DIALOG 9, 20, 261, 73
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "I/O redirection", IDC_STATIC, 7, 7, 251, 58
+    LTEXT           "Input (stdin):", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_STDIN, 70, 16, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_STDIN, 239, 15, 15, 14
+    LTEXT           "Output (stdout):", IDC_STATIC, 13, 34, 53, 8, SS_LEFT
+    EDITTEXT        IDC_STDOUT, 70, 32, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_STDOUT, 239, 31, 15, 14
+    LTEXT           "Error (stderr):", IDC_STATIC, 13, 50, 53, 8, SS_LEFT
+    EDITTEXT        IDC_STDERR, 70, 48, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_STDERR, 239, 47, 15, 14
+}
+
 
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -149,27 +215,32 @@ LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
 #pragma code_page(1252)
 #endif //_WIN32
 
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_NSSM                ICON                    "nssm.ico"
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog
 //
 
-IDD_INSTALL DIALOG  0, 0, 220, 90
-STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "Installation d'un service NSSM"
 LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
+IDD_INSTALL DIALOG 0, 0, 282, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "Installation d'un service NSSM"
 FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "Installer le service",IDC_OK,49,69,75,14
-    PUSHBUTTON      "Annuler",IDCANCEL,131,69,50,14
-    EDITTEXT        IDC_PATH,48,7,110,14,ES_AUTOHSCROLL
-    PUSHBUTTON      "Parcourir",IDC_BROWSE,163,7,50,14
-    EDITTEXT        IDC_FLAGS,48,28,165,14,ES_AUTOHSCROLL
-    LTEXT           "Options:",IDC_STATIC,7,31,27,8
-    LTEXT           "Nom du\nservice:",IDC_STATIC,7,49,41,18
-    EDITTEXT        IDC_NAME,48,49,77,14,ES_AUTOHSCROLL
-    LTEXT           "Application:",IDC_STATIC,7,9,38,8
-END
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Nom du service:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Installer le service", IDOK, 172, 106, 50, 14
+    PUSHBUTTON      "Anuller", IDCANCEL, 227, 106, 50, 14
+}
 
 IDD_REMOVE DIALOG  0, 0, 223, 28
 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
@@ -181,11 +252,54 @@ BEGIN
     EDITTEXT        IDC_NAME,43,7,90,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
+IDD_APPLICATION DIALOG 9, 20, 261, 73
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Application", IDC_STATIC, 7, 7, 251, 58
+    LTEXT           "Chemin:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 80, 16, 157, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE, 239, 15, 15, 14
+    LTEXT           "Rép. de démarrage:", IDC_STATIC, 13, 32, 64, 8, SS_LEFT
+    EDITTEXT        IDC_DIR, 80, 30, 157, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_DIR, 239, 30, 15, 14
+    LTEXT           "Options:", IDC_STATIC, 13, 47, 53, 8, SS_LEFT
+    EDITTEXT        IDC_FLAGS, 80, 45, 174, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // DESIGNINFO
 //
 
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION NSSM_VERSIONINFO
+ PRODUCTVERSION NSSM_VERSIONINFO
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+    END
+END
+
 #endif    // French resources
 /////////////////////////////////////////////////////////////////////////////
 
@@ -240,22 +354,18 @@ IDI_NSSM                ICON                    "nssm.ico"
 // Dialog
 //
 
-IDD_INSTALL DIALOG  0, 0, 220, 90
-STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "NSSM - Installazione Servizio"
 LANGUAGE LANG_ITALIAN, SUBLANG_ITALIAN
+IDD_INSTALL DIALOG 0, 0, 282, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "NSSM - Installazione Servizio"
 FONT 8, "MS Sans Serif"
-BEGIN
-    DEFPUSHBUTTON   "Installa servizio",IDC_OK,49,69,58,14
-    PUSHBUTTON      "Annulla",IDCANCEL,111,69,50,14
-    EDITTEXT        IDC_PATH,48,7,110,14,ES_AUTOHSCROLL
-    PUSHBUTTON      "Sfoglia...",IDC_BROWSE,163,7,50,14
-    EDITTEXT        IDC_FLAGS,48,28,165,14,ES_AUTOHSCROLL
-    LTEXT           "Opzioni:",IDC_STATIC,7,31,27,8
-    LTEXT           "Nome\nservizio:",IDC_STATIC,7,49,41,18
-    EDITTEXT        IDC_NAME,48,49,77,14,ES_AUTOHSCROLL
-    LTEXT           "Applicazione:",IDC_STATIC,2,9,43,8
-END
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Nome servizio:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Installa servizio", IDOK, 172, 106, 50, 14
+    PUSHBUTTON      "Anulla", IDCANCEL, 227, 106, 50, 14
+}
 
 IDD_REMOVE DIALOG  0, 0, 223, 28
 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
@@ -267,6 +377,22 @@ BEGIN
     EDITTEXT        IDC_NAME,59,7,87,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_ITALIAN, SUBLANG_ITALIAN
+IDD_APPLICATION DIALOG 9, 20, 261, 73
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Applicazione", IDC_STATIC, 7, 7, 251, 58
+    LTEXT           "Path:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 70, 16, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE, 239, 16, 15, 14
+    LTEXT           "Cartella di avvio:", IDC_STATIC, 13, 32, 53, 8, SS_LEFT
+    EDITTEXT        IDC_DIR, 70, 30, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+    DEFPUSHBUTTON   "...", IDC_BROWSE_DIR, 239, 30, 15, 14
+    LTEXT           "Opzioni:", IDC_STATIC, 13, 47, 53, 8, SS_LEFT
+    EDITTEXT        IDC_FLAGS, 70, 46, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
 
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -288,7 +414,33 @@ BEGIN
 END
 #endif    // APSTUDIO_INVOKED
 
-#endif    // English (U.K.) resources
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION NSSM_VERSIONINFO
+ PRODUCTVERSION NSSM_VERSIONINFO
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+    END
+END
+
+#endif    // Italian (Italy) resources
 /////////////////////////////////////////////////////////////////////////////
 
 
@@ -303,4 +455,3 @@ END
 #include "messages.rc"
 /////////////////////////////////////////////////////////////////////////////
 #endif    // not APSTUDIO_INVOKED
-

+ 19 - 8
registry.cpp

@@ -1,5 +1,7 @@
 #include "nssm.h"
 
+extern const char *exit_action_strings[];
+
 int create_messages() {
   HKEY key;
 
@@ -21,7 +23,7 @@ int create_messages() {
   /* Try to register the module but don't worry so much on failure */
   RegSetValueEx(key, "EventMessageFile", 0, REG_SZ, (const unsigned char *) path, (unsigned long) strlen(path) + 1);
   unsigned long types = EVENTLOG_INFORMATION_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_ERROR_TYPE;
-  RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, /*XXX*/(PBYTE) &types, sizeof(types));
+  RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, (const unsigned char *) &types, sizeof(types));
 
   return 0;
 }
@@ -42,26 +44,35 @@ int create_parameters(nssm_service_t *service) {
   }
 
   /* Try to create the parameters */
-  if (RegSetValueEx(key, NSSM_REG_EXE, 0, REG_EXPAND_SZ, (const unsigned char *) service->exe, (unsigned long) strlen(service->exe) + 1) != ERROR_SUCCESS) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXE, error_string(GetLastError()), 0);
+  if (set_expand_string(key, NSSM_REG_EXE, service->exe)) {
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
     return 3;
   }
-  if (RegSetValueEx(key, NSSM_REG_FLAGS, 0, REG_EXPAND_SZ, (const unsigned char *) service->flags, (unsigned long) strlen(service->flags) + 1) != ERROR_SUCCESS) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_FLAGS, error_string(GetLastError()), 0);
+  if (set_expand_string(key, NSSM_REG_FLAGS, service->flags)) {
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
     return 4;
   }
-  if (RegSetValueEx(key, NSSM_REG_DIR, 0, REG_EXPAND_SZ, (const unsigned char *) service->dir, (unsigned long) strlen(service->dir) + 1) != ERROR_SUCCESS) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_DIR, error_string(GetLastError()), 0);
+  if (set_expand_string(key, NSSM_REG_DIR, service->dir)) {
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
     return 5;
   }
 
-  /* Close registry */
+  /* Other non-default parameters. May fail. */
+  unsigned long stop_method_skip = ~service->stop_method;
+  if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);
+  if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action]);
+  if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);
+  if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);
+  if (service->kill_window_delay != NSSM_KILL_WINDOW_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, service->kill_window_delay);
+  if (service->kill_threads_delay != NSSM_KILL_THREADS_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, service->kill_threads_delay);
+  if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);
+  if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);
+  if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);
+
+  /* Close registry. */
   RegCloseKey(key);
 
   return 0;

+ 26 - 3
resource.h

@@ -2,24 +2,47 @@
 // Microsoft Developer Studio generated include file.
 // Used by nssm.rc
 //
+#define IDC_STATIC (-1)
 #define IDI_NSSM                   101
 #define IDD_INSTALL                   102
 #define IDD_REMOVE            103
+#define IDD_APPLICATION            104
+#define IDD_IO            105
+#define IDD_APPEXIT            106
+#define IDD_SHUTDOWN            107
 #define IDC_PATH                        1000
-#define IDC_OK                          1001
+#define IDC_TAB1                        1001
 #define IDC_CANCEL                      1002
 #define IDC_BROWSE                      1003
 #define IDC_FLAGS                       1004
 #define IDC_NAME                        1005
 #define IDC_REMOVE                      1007
+#define IDC_METHOD_CONSOLE              1008
+#define IDC_METHOD_WINDOW               1009
+#define IDC_METHOD_THREADS              1010
+#define IDC_METHOD_TERMINATE            1011
+#define IDC_KILL_CONSOLE                1012
+#define IDC_KILL_WINDOW                 1013
+#define IDC_KILL_THREADS                1014
+#define IDC_STDIN                       1015
+#define IDC_STDOUT                      1016
+#define IDC_STDERR                      1017
+#define IDC_BROWSE_STDIN                1018
+#define IDC_BROWSE_STDOUT               1019
+#define IDC_BROWSE_STDERR               1020
+#define IDC_THROTTLE                    1021
+#define IDC_APPEXIT                     1022
+#define IDC_DIR                         1023
+#define IDC_BROWSE_DIR                  1024
 
 // Next default values for new objects
 // 
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE        104
+#define _APS_NEXT_RESOURCE_VALUE        108
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1009
+#define _APS_NEXT_CONTROL_VALUE         1024
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif
+

+ 1 - 2
service.cpp

@@ -5,8 +5,7 @@ bool use_critical_section;
 
 extern imports_t imports;
 
-static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
-static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
+const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
 
 static inline int throttle_milliseconds(unsigned long throttle) {
   /* pow() operates on doubles. */

+ 4 - 0
service.h

@@ -22,6 +22,10 @@ typedef struct {
   char flags[VALUE_LENGTH];
   char dir[MAX_PATH];
   char *env;
+  char stdin_path[MAX_PATH];
+  char stdout_path[MAX_PATH];
+  char stderr_path[MAX_PATH];
+  unsigned long default_exit_action;
   unsigned long throttle_delay;
   unsigned long stop_method;
   unsigned long kill_console_delay;