Fixed bug when installing from the command line.
[nssm.git] / gui.cpp
diff --git a/gui.cpp b/gui.cpp
index 5041acd..ca21ace 100644 (file)
--- a/gui.cpp
+++ b/gui.cpp
@@ -1,13 +1,14 @@
 #include "nssm.h"\r
 \r
-int nssm_gui(int resource, char *name) {\r
-  char blurb[256];\r
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
+static HWND tablist[NSSM_NUM_TABS];\r
+static int selected_tab;\r
 \r
+int nssm_gui(int resource, char *name) {\r
   /* Create window */\r
   HWND dlg = CreateDialog(0, MAKEINTRESOURCE(resource), 0, install_dlg);\r
   if (! dlg) {\r
-    snprintf(blurb, sizeof(blurb), "CreateDialog() failed with error code %d", GetLastError());\r
-    MessageBox(0, blurb, NSSM, MB_OK);\r
+    popup_message(MB_OK, NSSM_GUI_CREATEDIALOG_FAILED, error_string(GetLastError()));\r
     return 1;\r
   }\r
 \r
@@ -31,11 +32,12 @@ int nssm_gui(int resource, char *name) {
   /* Go! */\r
   MSG message;\r
   while (GetMessage(&message, 0, 0, 0)) {\r
+    if (IsDialogMessage(dlg, &message)) continue;\r
     TranslateMessage(&message);\r
     DispatchMessage(&message);\r
   }\r
 \r
-  return message.wParam;\r
+  return (int) message.wParam;\r
 }\r
 \r
 void centre_window(HWND window) {\r
@@ -58,63 +60,197 @@ void centre_window(HWND window) {
   /* Centre window */\r
   x = (desktop_size.right - size.right) / 2;\r
   y = (desktop_size.bottom - size.bottom) / 2;\r
-  MoveWindow(window, x, y, size.right, size.bottom, 0);\r
+  MoveWindow(window, x, y, size.right - size.left, size.bottom - size.top, 0);\r
+}\r
+\r
+static inline void check_stop_method(nssm_service_t *service, unsigned long method, unsigned long control) {\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) return;\r
+  service->stop_method &= ~method;\r
+}\r
+\r
+static inline void check_method_timeout(HWND tab, unsigned long control, unsigned long *timeout) {\r
+  BOOL translated;\r
+  unsigned long configured = GetDlgItemInt(tab, control, &translated, 0);\r
+  if (translated) *timeout = configured;\r
 }\r
 \r
-/* Install the service */\r
+static inline void check_io(char *name, char *buffer, size_t bufsize, unsigned long control) {\r
+  if (! SendMessage(GetDlgItem(tablist[NSSM_TAB_IO], control), WM_GETTEXTLENGTH, 0, 0)) return;\r
+  if (GetDlgItemText(tablist[NSSM_TAB_IO], control, buffer, (int) bufsize)) return;\r
+  popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, name);\r
+  ZeroMemory(buffer, bufsize);\r
+}\r
+\r
+/* Install the service. */\r
 int install(HWND window) {\r
   if (! window) return 1;\r
 \r
-  /* Check parameters in the window */\r
-  char name[STRING_SIZE];\r
-  char exe[MAX_PATH];\r
-  char flags[STRING_SIZE];\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (service) {\r
+    set_nssm_service_defaults(service);\r
 \r
-  /* Get service name */\r
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {\r
-    MessageBox(0, "No valid service name was specified!", NSSM, MB_OK);\r
-    return 2;\r
-  }\r
+    /* Get service name. */\r
+    if (! GetDlgItemText(window, IDC_NAME, service->name, sizeof(service->name))) {\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
+      cleanup_nssm_service(service);\r
+      return 2;\r
+    }\r
 \r
-  /* Get executable name */\r
-  if (! GetDlgItemText(window, IDC_PATH, exe, sizeof(exe))) {\r
-    MessageBox(0, "No valid executable path was specified!", NSSM, MB_OK);\r
-    return 3;\r
-  }\r
+    /* Get executable name */\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe, sizeof(service->exe))) {\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);\r
+      return 3;\r
+    }\r
 \r
-  /* Get flags */\r
-  if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {\r
-    if (! GetDlgItemText(window, IDC_FLAGS, flags, sizeof(flags))) {\r
-      MessageBox(0, "No valid options were specified!", NSSM, MB_OK);\r
-      return 4;\r
+    /* Get startup directory. */\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_DIR, service->dir, sizeof(service->dir))) {\r
+      memmove(service->dir, service->exe, sizeof(service->dir));\r
+      strip_basename(service->dir);\r
+    }\r
+\r
+    /* Get flags. */\r
+    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {\r
+      if (! GetDlgItemText(window, IDC_FLAGS, service->flags, sizeof(service->flags))) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);\r
+        return 4;\r
+      }\r
+    }\r
+\r
+    /* Get stop method stuff. */\r
+    check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);\r
+    check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);\r
+    check_stop_method(service, NSSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS);\r
+    check_stop_method(service, NSSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE);\r
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);\r
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);\r
+    check_method_timeout(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);\r
+\r
+    /* Get exit action stuff. */\r
+    check_method_timeout(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);\r
+    HWND combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
+    service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);\r
+    if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;\r
+\r
+    /* Get I/O stuff. */\r
+    check_io("stdin", service->stdin_path, sizeof(service->stdin_path), IDC_STDIN);\r
+    check_io("stdout", service->stdout_path, sizeof(service->stdout_path), IDC_STDOUT);\r
+    check_io("stderr", service->stderr_path, sizeof(service->stderr_path), IDC_STDERR);\r
+    /* Override stdout and/or stderr. */\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+      if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS;\r
+      if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS;\r
+    }\r
+\r
+    /* Get environment. */\r
+    unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);\r
+    if (envlen) {\r
+      char *env = (char *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, envlen + 2);\r
+      if (! env) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "environment", "install()");\r
+        cleanup_nssm_service(service);\r
+        return 5;\r
+      }\r
+\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, env, envlen + 1)) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);\r
+        HeapFree(GetProcessHeap(), 0, env);\r
+        cleanup_nssm_service(service);\r
+        return 5;\r
+      }\r
+\r
+      /* Strip CR and replace LF with NULL. */\r
+      unsigned long newlen = 0;\r
+      unsigned long i, j;\r
+      for (i = 0; i < envlen; i++) if (env[i] != '\r') newlen++;\r
+      /* Must end with two NULLs. */\r
+      newlen += 2;\r
+\r
+      char *newenv = (char *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen);\r
+      if (! newenv) {\r
+        HeapFree(GetProcessHeap(), 0, env);\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "environment", "install()");\r
+        cleanup_nssm_service(service);\r
+        return 5;\r
+      }\r
+\r
+      for (i = 0, j = 0; i < envlen; i++) {\r
+        if (env[i] == '\r') continue;\r
+        if (env[i] == '\n') newenv[j] = '\0';\r
+        else newenv[j] = env[i];\r
+        j++;\r
+      }\r
+\r
+      HeapFree(GetProcessHeap(), 0, env);\r
+      env = newenv;\r
+      envlen = newlen;\r
+\r
+      /* Test the environment is valid. */\r
+      char path[MAX_PATH];\r
+      GetModuleFileName(0, path, sizeof(path));\r
+      STARTUPINFO si;\r
+      ZeroMemory(&si, sizeof(si));\r
+      si.cb = sizeof(si);\r
+      PROCESS_INFORMATION pi;\r
+      ZeroMemory(&pi, sizeof(pi));\r
+\r
+      if (! CreateProcess(0, path, 0, 0, 0, CREATE_SUSPENDED, env, 0, &si, &pi)) {\r
+        unsigned long error = GetLastError();\r
+        if (error == ERROR_INVALID_PARAMETER) {\r
+          popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);\r
+          HeapFree(GetProcessHeap(), 0, env);\r
+          envlen = 0;\r
+        }\r
+        cleanup_nssm_service(service);\r
+        return 5;\r
+      }\r
+      TerminateProcess(pi.hProcess, 0);\r
+\r
+      if (SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+        service->env = env;\r
+        service->envlen = envlen;\r
+      }\r
+      else {\r
+        service->env_extra = env;\r
+        service->env_extralen = envlen;\r
+      }\r
     }\r
   }\r
-  else ZeroMemory(&flags, sizeof(flags));\r
 \r
-  /* See if it works */\r
-  switch (install_service(name, exe, flags)) {\r
+  /* See if it works. */\r
+  switch (install_service(service)) {\r
+    case 1:\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "service", "install()");\r
+      cleanup_nssm_service(service);\r
+      return 1;\r
+\r
     case 2:\r
-      MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      MessageBox(0, "Path too long!\nThe full path to " NSSM " is too long.\nPlease install " NSSM " somewhere else...\n", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
+      cleanup_nssm_service(service);\r
       return 3;\r
 \r
     case 4:\r
-      MessageBox(0, "Error constructing ImagePath!\nThis really shouldn't happen.  You could be out of memory\nor the world may be about to end or something equally bad.", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
+      cleanup_nssm_service(service);\r
       return 4;\r
 \r
     case 5:\r
-      MessageBox(0, "Couldn't create service!\nPerhaps it is already installed...", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INSTALL_SERVICE_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 5;\r
 \r
     case 6:\r
-      MessageBox(0, "Couldn't set startup parameters for the service!\nDeleting the service...", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_CREATE_PARAMETERS_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 6;\r
   }\r
 \r
-  MessageBox(0, "Service successfully installed!", NSSM, MB_OK);\r
+  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
+  cleanup_nssm_service(service);\r
   return 0;\r
 }\r
 \r
@@ -122,90 +258,303 @@ int install(HWND window) {
 int remove(HWND window) {\r
   if (! window) return 1;\r
 \r
-  /* Check parameters in the window */\r
-  char name[STRING_SIZE];\r
+  /* See if it works */\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (service) {\r
+    /* Get service name */\r
+    if (! GetDlgItemText(window, IDC_NAME, service->name, sizeof(service->name))) {\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
+      cleanup_nssm_service(service);\r
+      return 2;\r
+    }\r
 \r
-  /* Get service name */\r
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {\r
-    MessageBox(0, "No valid service name was specified!", NSSM, MB_OK);\r
-    return 2;\r
+    /* Confirm */\r
+    if (popup_message(MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) {\r
+      cleanup_nssm_service(service);\r
+      return 0;\r
+    }\r
   }\r
 \r
-  /* Confirm */\r
-  char blurb[MAX_PATH];\r
-  if (snprintf(blurb, sizeof(blurb), "Remove the \"%s\" service?", name) < 0) {\r
-    if (MessageBox(0, "Remove the service?", NSSM, MB_YESNO) != IDYES) return 0;\r
-  }\r
-  else if (MessageBox(0, blurb, NSSM, MB_YESNO) != IDYES) return 0;\r
+  switch (remove_service(service)) {\r
+    case 1:\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "service", "remove()");\r
+      cleanup_nssm_service(service);\r
+      return 1;\r
 \r
-  /* See if it works */\r
-  switch (remove_service(name)) {\r
     case 2:\r
-      MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      MessageBox(0, "Can't open service!\nPerhaps it isn't installed...", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_SERVICE_NOT_INSTALLED);\r
       return 3;\r
+      cleanup_nssm_service(service);\r
 \r
     case 4:\r
-      MessageBox(0, "Can't delete service!  Make sure the service is stopped and try again.\nIf this error persists, you may need to set the service NOT to start\nautomatically, reboot your computer and try removing it again.", NSSM, MB_OK);\r
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_REMOVE_SERVICE_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 4;\r
   }\r
 \r
-  MessageBox(0, "Service successfully removed!", NSSM, MB_OK);\r
+  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
+  cleanup_nssm_service(service);\r
   return 0;\r
 }\r
 \r
-/* Browse for game */\r
-void browse(HWND window) {\r
+static char *browse_filter(int message) {\r
+  switch (message) {\r
+    case NSSM_GUI_BROWSE_FILTER_APPLICATIONS: return "*.exe;*.bat;*.cmd";\r
+    case NSSM_GUI_BROWSE_FILTER_DIRECTORIES: return ".";\r
+    case NSSM_GUI_BROWSE_FILTER_ALL_FILES: /* Fall through. */\r
+    default: return "*.*";\r
+  }\r
+}\r
+\r
+UINT_PTR CALLBACK browse_hook(HWND dlg, UINT message, WPARAM w, LPARAM l) {\r
+  switch (message) {\r
+    case WM_INITDIALOG:\r
+      return 1;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+/* Browse for application */\r
+void browse(HWND window, char *current, unsigned long flags, ...) {\r
   if (! window) return;\r
 \r
+  va_list arg;\r
+  size_t bufsize = 256;\r
+  size_t len = bufsize;\r
+  int i;\r
+\r
   OPENFILENAME ofn;\r
   ZeroMemory(&ofn, sizeof(ofn));\r
   ofn.lStructSize = sizeof(ofn);\r
-  ofn.lpstrFilter = "Applications\0*.exe\0All files\0*.*\0\0";\r
+  ofn.lpstrFilter = (char *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
+  /* XXX: Escaping nulls with FormatMessage is tricky */\r
+  if (ofn.lpstrFilter) {\r
+    ZeroMemory((void *) ofn.lpstrFilter, bufsize);\r
+    len = 0;\r
+    /* "Applications" + NULL + "*.exe" + NULL */\r
+    va_start(arg, flags);\r
+    while (i = va_arg(arg, int)) {\r
+      char *localised = message_string(i);\r
+      _snprintf_s((char *) ofn.lpstrFilter + len, bufsize, _TRUNCATE, localised);\r
+      len += strlen(localised) + 1;\r
+      LocalFree(localised);\r
+      char *filter = browse_filter(i);\r
+      _snprintf_s((char *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, "%s", filter);\r
+      len += strlen(filter) + 1;\r
+    }\r
+    va_end(arg);\r
+    /* Remainder of the buffer is already zeroed */\r
+  }\r
   ofn.lpstrFile = new char[MAX_PATH];\r
-  ofn.lpstrFile[0] = '\0';\r
-  ofn.lpstrTitle = "Locate application file";\r
+  if (flags & OFN_NOVALIDATE) {\r
+    /* Directory hack. */\r
+    _snprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, ":%s:", message_string(NSSM_GUI_BROWSE_FILTER_DIRECTORIES));\r
+  }\r
+  else _snprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, "%s", current);\r
+  ofn.lpstrTitle = message_string(NSSM_GUI_BROWSE_TITLE);\r
   ofn.nMaxFile = MAX_PATH;\r
-  ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;\r
+  ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | flags;\r
 \r
   if (GetOpenFileName(&ofn)) {\r
+    /* Directory hack. */\r
+    if (flags & OFN_NOVALIDATE) strip_basename(ofn.lpstrFile);\r
     SendMessage(window, WM_SETTEXT, 0, (LPARAM) ofn.lpstrFile);\r
   }\r
+  if (ofn.lpstrFilter) HeapFree(GetProcessHeap(), 0, (void *) ofn.lpstrFilter);\r
 \r
   delete[] ofn.lpstrFile;\r
 }\r
 \r
+INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {\r
+  switch (message) {\r
+    case WM_INITDIALOG:\r
+      return 1;\r
+\r
+    /* Button was pressed or control was controlled. */\r
+    case WM_COMMAND:\r
+      HWND dlg;\r
+      char buffer[MAX_PATH];\r
+\r
+      switch (LOWORD(w)) {\r
+        /* Browse for application. */\r
+        case IDC_BROWSE:\r
+          dlg = GetDlgItem(tab, IDC_PATH);\r
+          GetDlgItemText(tab, IDC_PATH, buffer, sizeof(buffer));\r
+          browse(dlg, buffer, OFN_FILEMUSTEXIST, NSSM_GUI_BROWSE_FILTER_APPLICATIONS, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          /* Fill in startup directory if it wasn't already specified. */\r
+          GetDlgItemText(tab, IDC_DIR, buffer, sizeof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_PATH, buffer, sizeof(buffer));\r
+            strip_basename(buffer);\r
+            SetDlgItemText(tab, IDC_DIR, buffer);\r
+          }\r
+          break;\r
+\r
+          /* Browse for startup directory. */\r
+        case IDC_BROWSE_DIR:\r
+          dlg = GetDlgItem(tab, IDC_DIR);\r
+          GetDlgItemText(tab, IDC_DIR, buffer, sizeof(buffer));\r
+          browse(dlg, buffer, OFN_NOVALIDATE, NSSM_GUI_BROWSE_FILTER_DIRECTORIES, 0);\r
+          break;\r
+\r
+        /* Browse for stdin. */\r
+        case IDC_BROWSE_STDIN:\r
+          dlg = GetDlgItem(tab, IDC_STDIN);\r
+          GetDlgItemText(tab, IDC_STDIN, buffer, sizeof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+\r
+        /* Browse for stdout. */\r
+        case IDC_BROWSE_STDOUT:\r
+          dlg = GetDlgItem(tab, IDC_STDOUT);\r
+          GetDlgItemText(tab, IDC_STDOUT, buffer, sizeof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          /* Fill in stderr if it wasn't already specified. */\r
+          GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_STDOUT, buffer, sizeof(buffer));\r
+            SetDlgItemText(tab, IDC_STDERR, buffer);\r
+          }\r
+          break;\r
+\r
+        /* Browse for stderr. */\r
+        case IDC_BROWSE_STDERR:\r
+          dlg = GetDlgItem(tab, IDC_STDERR);\r
+          GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+      }\r
+      return 1;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
 /* Install/remove dialogue callback */\r
-int CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {\r
+INT_PTR CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {\r
   switch (message) {\r
     /* Creating the dialogue */\r
     case WM_INITDIALOG:\r
+      SetFocus(GetDlgItem(window, IDC_NAME));\r
+\r
+      HWND tabs;\r
+      HWND combo;\r
+      tabs = GetDlgItem(window, IDC_TAB1);\r
+      if (! tabs) return 0;\r
+\r
+      /* Set up tabs. */\r
+      TCITEM tab;\r
+      ZeroMemory(&tab, sizeof(tab));\r
+      tab.mask = TCIF_TEXT;\r
+\r
+      /* Application tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_APPLICATION);\r
+      tab.cchTextMax = (int) strlen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_APPLICATION, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_APPLICATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_APPLICATION], SW_SHOW);\r
+\r
+      /* Shutdown tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
+      tab.cchTextMax = (int) strlen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_SHUTDOWN, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_SHUTDOWN] = CreateDialog(0, MAKEINTRESOURCE(IDD_SHUTDOWN), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_SHUTDOWN], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, NSSM_KILL_CONSOLE_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, NSSM_KILL_WINDOW_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, NSSM_KILL_THREADS_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_CHECKED, 0);\r
+\r
+      /* Restart tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_EXIT);\r
+      tab.cchTextMax = (int) strlen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_EXIT, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_EXIT] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPEXIT), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_EXIT], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, NSSM_RESET_THROTTLE_RESTART, 0);\r
+      combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_RESTART, (LPARAM) message_string(NSSM_GUI_EXIT_RESTART));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_IGNORE, (LPARAM) message_string(NSSM_GUI_EXIT_IGNORE));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_REALLY, (LPARAM) message_string(NSSM_GUI_EXIT_REALLY));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_UNCLEAN, (LPARAM) message_string(NSSM_GUI_EXIT_UNCLEAN));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_EXIT_RESTART, 0);\r
+\r
+      /* I/O tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_IO);\r
+      tab.cchTextMax = (int) strlen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_IO, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_IO] = CreateDialog(0, MAKEINTRESOURCE(IDD_IO), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE);\r
+\r
+      /* Environment tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_ENVIRONMENT);\r
+      tab.cchTextMax = (int) strlen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ENVIRONMENT, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_ENVIRONMENT] = CreateDialog(0, MAKEINTRESOURCE(IDD_ENVIRONMENT), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_ENVIRONMENT], SW_HIDE);\r
+\r
+      selected_tab = 0;\r
+\r
       return 1;\r
 \r
+    /* Tab change. */\r
+    case WM_NOTIFY:\r
+      NMHDR *notification;\r
+\r
+      notification = (NMHDR *) l;\r
+      switch (notification->code) {\r
+        case TCN_SELCHANGE:\r
+          HWND tabs;\r
+          int selection;\r
+\r
+          tabs = GetDlgItem(window, IDC_TAB1);\r
+          if (! tabs) return 0;\r
+\r
+          selection = (int) SendMessage(tabs, TCM_GETCURSEL, 0, 0);\r
+          if (selection != selected_tab) {\r
+            ShowWindow(tablist[selected_tab], SW_HIDE);\r
+            /*\r
+              XXX: Sets focus to the service name which isn't ideal but is\r
+                   better than leaving it in another tab.\r
+            */\r
+            ShowWindow(tablist[selection], SW_SHOWDEFAULT);\r
+            SetFocus(tablist[selection]);\r
+            selected_tab = selection;\r
+          }\r
+          return 1;\r
+      }\r
+\r
+      return 0;\r
+\r
     /* Button was pressed or control was controlled */\r
     case WM_COMMAND:\r
       switch (LOWORD(w)) {\r
         /* OK button */\r
-        case IDC_OK:\r
-          PostQuitMessage(install(window));\r
+        case IDOK:\r
+          if (! install(window)) PostQuitMessage(0);\r
           break;\r
 \r
         /* Cancel button */\r
-        case IDC_CANCEL:\r
+        case IDCANCEL:\r
           DestroyWindow(window);\r
           break;\r
 \r
-        /* Browse button */\r
-        case IDC_BROWSE:\r
-          browse(GetDlgItem(window, IDC_PATH));\r
-          break;\r
-\r
         /* Remove button */\r
         case IDC_REMOVE:\r
-          PostQuitMessage(remove(window));\r
+          if (! remove(window)) PostQuitMessage(0);\r
           break;\r
       }\r
       return 1;\r