Set log on details in the GUI.
[nssm.git] / gui.cpp
diff --git a/gui.cpp b/gui.cpp
index 83f2165..80d74ef 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_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, 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, TCHAR *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,6 +32,7 @@ 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
@@ -47,7 +49,7 @@ void centre_window(HWND window) {
 \r
   /* Find window size */\r
   if (! GetWindowRect(window, &size)) return;\r
-  \r
+\r
   /* Find desktop window */\r
   desktop = GetDesktopWindow();\r
   if (! desktop) return;\r
@@ -61,60 +63,331 @@ void centre_window(HWND window) {
   MoveWindow(window, x, y, size.right - size.left, size.bottom - size.top, 0);\r
 }\r
 \r
-/* Install the service */\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_number(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
+static inline void set_timeout_enabled(unsigned long control, unsigned long dependent) {\r
+  unsigned char enabled = 0;\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1;\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], dependent), enabled);\r
+}\r
+\r
+static inline void set_logon_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), enabled);\r
+}\r
+\r
+static inline void set_rotation_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled);\r
+}\r
+\r
+static inline void check_io(TCHAR *name, TCHAR *buffer, unsigned long len, 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) len)) return;\r
+  popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, name);\r
+  ZeroMemory(buffer, len * sizeof(TCHAR));\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[EXE_LENGTH];\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, _countof(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, _countof(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, _countof(service->dir))) {\r
+      _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\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(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS, service->flags, _countof(service->flags))) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);\r
+        return 4;\r
+      }\r
+    }\r
+\r
+    /* Get details. */\r
+    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME), WM_GETTEXTLENGTH, 0, 0)) {\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname, _countof(service->displayname))) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DISPLAYNAME);\r
+        return 5;\r
+      }\r
+    }\r
+\r
+    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION), WM_GETTEXTLENGTH, 0, 0)) {\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description, _countof(service->description))) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DESCRIPTION);\r
+        return 5;\r
+      }\r
+    }\r
+\r
+    HWND combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);\r
+    service->startup = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);\r
+    if (service->startup == CB_ERR) service->startup = 0;\r
+\r
+    /* Get logon stuff. */\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+      if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+        service->type |= SERVICE_INTERACTIVE_PROCESS;\r
+      }\r
+    }\r
+    else {\r
+      /* Username. */\r
+      service->usernamelen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0);\r
+      if (! service->usernamelen) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_USERNAME);\r
+        return 6;\r
+      }\r
+      service->usernamelen++;\r
+\r
+      service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));\r
+      if (! service->username) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));\r
+        return 6;\r
+      }\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username, (int) service->usernamelen)) {\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_USERNAME);\r
+        return 6;\r
+      }\r
+\r
+      /* Password. */\r
+      service->passwordlen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), WM_GETTEXTLENGTH, 0, 0);\r
+      if (! service->passwordlen) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+        return 6;\r
+      }\r
+      if (SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), WM_GETTEXTLENGTH, 0, 0) != service->passwordlen) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+        return 6;\r
+      }\r
+      service->passwordlen++;\r
+\r
+      /* Temporary buffer for password validation. */\r
+      TCHAR *password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR));\r
+      if (! password) {\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password confirmation"), _T("install()"));\r
+        return 6;\r
+      }\r
+\r
+      /* Actual password buffer. */\r
+      service->password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR));\r
+      if (! service->password) {\r
+        HeapFree(GetProcessHeap(), 0, password);\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password"), _T("install()"));\r
+        return 6;\r
+      }\r
+\r
+      /* Get first password. */\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1, service->password, (int) service->passwordlen)) {\r
+        HeapFree(GetProcessHeap(), 0, password);\r
+        SecureZeroMemory(service->password, service->passwordlen);\r
+        HeapFree(GetProcessHeap(), 0, service->password);\r
+        service->password = 0;\r
+        service->passwordlen = 0;\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_PASSWORD);\r
+        return 6;\r
+      }\r
+\r
+      /* Get confirmation. */\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2, password, (int) service->passwordlen)) {\r
+        SecureZeroMemory(password, service->passwordlen);\r
+        HeapFree(GetProcessHeap(), 0, password);\r
+        SecureZeroMemory(service->password, service->passwordlen);\r
+        HeapFree(GetProcessHeap(), 0, service->password);\r
+        service->password = 0;\r
+        service->passwordlen = 0;\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_PASSWORD);\r
+        return 6;\r
+      }\r
+\r
+      /* Compare. */\r
+      if (_tcsncmp(password, service->password, service->passwordlen)) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+        SecureZeroMemory(password, service->passwordlen);\r
+        HeapFree(GetProcessHeap(), 0, password);\r
+        SecureZeroMemory(service->password, service->passwordlen);\r
+        HeapFree(GetProcessHeap(), 0, service->password);\r
+        service->password = 0;\r
+        service->passwordlen = 0;\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+        return 6;\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_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);\r
+    check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);\r
+    check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);\r
+\r
+    /* Get exit action stuff. */\r
+    check_number(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);\r
+    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(_T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);\r
+    check_io(_T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);\r
+    check_io(_T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR);\r
+\r
+    /* Override stdout and/or stderr. */\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], 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 rotation stuff. */\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+      service->rotate_files = true;\r
+      check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);\r
+      check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);\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
+      TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (envlen + 2) * sizeof(TCHAR));\r
+      if (! env) {\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("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] != _T('\r')) newlen++;\r
+      /* Must end with two NULLs. */\r
+      newlen += 2;\r
+\r
+      TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen * sizeof(TCHAR));\r
+      if (! newenv) {\r
+        HeapFree(GetProcessHeap(), 0, env);\r
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));\r
+        cleanup_nssm_service(service);\r
+        return 5;\r
+      }\r
+\r
+      for (i = 0, j = 0; i < envlen; i++) {\r
+        if (env[i] == _T('\r')) continue;\r
+        if (env[i] == _T('\n')) newenv[j] = _T('\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
+      if (test_environment(env)) {\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
+      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, _T("service"), _T("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 +395,371 @@ 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, _countof(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, _T("service"), _T("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 TCHAR *browse_filter(int message) {\r
+  switch (message) {\r
+    case NSSM_GUI_BROWSE_FILTER_APPLICATIONS: return _T("*.exe;*.bat;*.cmd");\r
+    case NSSM_GUI_BROWSE_FILTER_DIRECTORIES: return _T(".");\r
+    case NSSM_GUI_BROWSE_FILTER_ALL_FILES: /* Fall through. */\r
+    default: return _T("*.*");\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, TCHAR *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.lpstrFile = new char[MAX_PATH];\r
-  ofn.lpstrFile[0] = '\0';\r
-  ofn.lpstrTitle = "Locate application file";\r
+  ofn.lpstrFilter = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, bufsize * sizeof(TCHAR));\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
+      TCHAR *localised = message_string(i);\r
+      _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize, _TRUNCATE, localised);\r
+      len += _tcslen(localised) + 1;\r
+      LocalFree(localised);\r
+      TCHAR *filter = browse_filter(i);\r
+      _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, _T("%s"), filter);\r
+      len += _tcslen(filter) + 1;\r
+    }\r
+    va_end(arg);\r
+    /* Remainder of the buffer is already zeroed */\r
+  }\r
+  ofn.lpstrFile = new TCHAR[MAX_PATH];\r
+  if (flags & OFN_NOVALIDATE) {\r
+    /* Directory hack. */\r
+    _sntprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, _T(":%s:"), message_string(NSSM_GUI_BROWSE_FILTER_DIRECTORIES));\r
+  }\r
+  else _sntprintf_s(ofn.lpstrFile, MAX_PATH, _TRUNCATE, _T("%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
+      TCHAR buffer[MAX_PATH];\r
+      unsigned char enabled;\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, _countof(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, _countof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_PATH, buffer, _countof(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, _countof(buffer));\r
+          browse(dlg, buffer, OFN_NOVALIDATE, NSSM_GUI_BROWSE_FILTER_DIRECTORIES, 0);\r
+          break;\r
+\r
+        /* Log on. */\r
+        case IDC_LOCALSYSTEM:\r
+          set_logon_enabled(0);\r
+          break;\r
+\r
+        case IDC_ACCOUNT:\r
+          set_logon_enabled(1);\r
+          break;\r
+\r
+        /* Shutdown methods. */\r
+        case IDC_METHOD_CONSOLE:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_CONSOLE);\r
+          break;\r
+\r
+        case IDC_METHOD_WINDOW:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_WINDOW);\r
+          break;\r
+\r
+        case IDC_METHOD_THREADS:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_THREADS);\r
+          break;\r
+\r
+        /* Browse for stdin. */\r
+        case IDC_BROWSE_STDIN:\r
+          dlg = GetDlgItem(tab, IDC_STDIN);\r
+          GetDlgItemText(tab, IDC_STDIN, buffer, _countof(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, _countof(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, _countof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_STDOUT, buffer, _countof(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, _countof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+\r
+        /* Rotation. */\r
+        case IDC_ROTATE:\r
+          if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1;\r
+          else enabled = 0;\r
+          set_rotation_enabled(enabled);\r
+          break;\r
+      }\r
+      return 1;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
 /* Install/remove dialogue callback */\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) _tcslen(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
+      /* Details tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_DETAILS);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_DETAILS, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_DETAILS] = CreateDialog(0, MAKEINTRESOURCE(IDD_DETAILS), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_DETAILS], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_AUTOMATIC, (LPARAM) message_string(NSSM_GUI_STARTUP_AUTOMATIC));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_DELAYED, (LPARAM) message_string(NSSM_GUI_STARTUP_DELAYED));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_MANUAL, (LPARAM) message_string(NSSM_GUI_STARTUP_MANUAL));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_DISABLED, (LPARAM) message_string(NSSM_GUI_STARTUP_DISABLED));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_STARTUP_AUTOMATIC, 0);\r
+\r
+      /* Logon tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_LOGON);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_LOGON, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_LOGON] = CreateDialog(0, MAKEINTRESOURCE(IDD_LOGON), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_LOGON], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);\r
+      set_logon_enabled(0);\r
+\r
+      /* Shutdown tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
+      tab.cchTextMax = (int) _tcslen(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) _tcslen(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) _tcslen(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
+      /* Rotation tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_ROTATION);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ROTATION, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_ROTATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_ROTATION), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0);\r
+      set_rotation_enabled(0);\r
+\r
+      /* Environment tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_ENVIRONMENT);\r
+      tab.cchTextMax = (int) _tcslen(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