Rotate files while the service is running.
[nssm.git] / gui.cpp
diff --git a/gui.cpp b/gui.cpp
index d9b7011..c09b363 100644 (file)
--- a/gui.cpp
+++ b/gui.cpp
@@ -1,6 +1,6 @@
 #include "nssm.h"\r
 \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 enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_PROCESS, 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
@@ -27,10 +27,16 @@ int nssm_gui(int resource, nssm_service_t *service) {
   /* Create window */\r
   HWND dlg = dialog(MAKEINTRESOURCE(resource), 0, nssm_dlg, (LPARAM) service);\r
   if (! dlg) {\r
-    popup_message(MB_OK, NSSM_GUI_CREATEDIALOG_FAILED, error_string(GetLastError()));\r
+    popup_message(0, MB_OK, NSSM_GUI_CREATEDIALOG_FAILED, error_string(GetLastError()));\r
     return 1;\r
   }\r
 \r
+  /* Load the icon. */\r
+  HANDLE icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_NSSM), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);\r
+  if (icon) SendMessage(dlg, WM_SETICON, ICON_SMALL, (LPARAM) icon);\r
+  icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_NSSM), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0);\r
+  if (icon) SendMessage(dlg, WM_SETICON, ICON_BIG, (LPARAM) icon);\r
+\r
   /* Remember what the window is for. */\r
   SetWindowLongPtr(dlg, GWLP_USERDATA, (LONG_PTR) resource);\r
 \r
@@ -61,6 +67,7 @@ int nssm_gui(int resource, nssm_service_t *service) {
 \r
     /* Set existing details. */\r
     HWND combo;\r
+    HWND list;\r
 \r
     /* Application tab. */\r
     if (service->native) SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->image);\r
@@ -88,6 +95,28 @@ int nssm_gui(int resource, nssm_service_t *service) {
       if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0);\r
     }\r
 \r
+    /* Process tab. */\r
+    if (service->priority) {\r
+      int priority = priority_constant_to_index(service->priority);\r
+      combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+      SendMessage(combo, CB_SETCURSEL, priority, 0);\r
+    }\r
+\r
+    if (service->affinity) {\r
+      list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), 1);\r
+\r
+      DWORD_PTR affinity, system_affinity;\r
+      if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
+        if ((service->affinity & (__int64) system_affinity) != service->affinity) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_AFFINITY);\r
+      }\r
+\r
+      for (int i = 0; i < num_cpus(); i++) {\r
+        if (! (service->affinity & (1LL << (__int64) i))) SendMessage(list, LB_SETSEL, 0, i);\r
+      }\r
+    }\r
+\r
     /* Shutdown tab. */\r
     if (! (service->stop_method & NSSM_STOP_METHOD_CONSOLE)) {\r
       SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);\r
@@ -112,6 +141,7 @@ int nssm_gui(int resource, nssm_service_t *service) {
     SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, service->throttle_delay, 0);\r
     combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
     SendMessage(combo, CB_SETCURSEL, service->default_exit_action, 0);\r
+    SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, service->restart_delay, 0);\r
 \r
     /* I/O tab. */\r
     SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path);\r
@@ -122,53 +152,43 @@ int nssm_gui(int resource, nssm_service_t *service) {
     if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0);\r
     if (service->rotate_files) {\r
       SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, BST_CHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), 1);\r
       EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), 1);\r
       EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), 1);\r
     }\r
+    if (service->rotate_stdout_online || service->rotate_stderr_online) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_CHECKED, 0);\r
     SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0);\r
     if (! service->rotate_bytes_high) SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0);\r
 \r
     /* Check if advanced settings are in use. */\r
-    if (service->stdout_disposition ^ service->stderr_disposition || service->stdout_disposition & ~CREATE_ALWAYS || service->stderr_disposition & ~CREATE_ALWAYS) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_STDIO);\r
-    if (service->rotate_bytes_high) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES);\r
+    if (service->stdout_disposition ^ service->stderr_disposition || service->stdout_disposition & ~CREATE_ALWAYS || service->stderr_disposition & ~CREATE_ALWAYS) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_STDIO);\r
+    if (service->rotate_bytes_high) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES);\r
 \r
     /* Environment tab. */\r
     TCHAR *env;\r
     unsigned long envlen;\r
     if (service->env_extralen) {\r
-      SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_SETCHECK, BST_CHECKED, 0);\r
       env = service->env_extra;\r
       envlen = service->env_extralen;\r
     }\r
     else {\r
       env = service->env;\r
       envlen = service->envlen;\r
+      if (envlen) SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_SETCHECK, BST_CHECKED, 0);\r
     }\r
 \r
     if (envlen) {\r
-      /* Replace NULL with CRLF. Leave NULL NULL as the end marker. */\r
-      unsigned long i, j;\r
-      unsigned long newlen = envlen;\r
-      for (i = 0; i < envlen; i++) if (! env[i] && env[i + 1]) newlen++;\r
-\r
-      TCHAR *formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen * sizeof(TCHAR));\r
-      if (formatted) {\r
-        for (i = 0, j = 0; i < envlen; i++) {\r
-          formatted[j] = env[i];\r
-          if (! env[i]) {\r
-            if (env[i + 1]) {\r
-              formatted[j] = _T('\r');\r
-              formatted[++j] = _T('\n');\r
-            }\r
-          }\r
-          j++;\r
-        }\r
+      TCHAR *formatted;\r
+      unsigned long newlen;\r
+      if (format_environment(env, envlen, &formatted, &newlen)) {\r
+        popup_message(dlg, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("nssm_dlg()"));\r
+      }\r
+      else {\r
         SetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, formatted);\r
         HeapFree(GetProcessHeap(), 0, formatted);\r
       }\r
-      else popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("nssm_dlg()"));\r
     }\r
-    if (service->envlen && service->env_extralen) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ENVIRONMENT);\r
+    if (service->envlen && service->env_extralen) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ENVIRONMENT);\r
   }\r
 \r
   /* Go! */\r
@@ -229,15 +249,20 @@ static inline void set_logon_enabled(unsigned char enabled) {
   EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), enabled);\r
 }\r
 \r
+static inline void set_affinity_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), enabled);\r
+}\r
+\r
 static inline void set_rotation_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), 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
+static inline void check_io(HWND owner, 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
+  popup_message(owner, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, name);\r
   ZeroMemory(buffer, len * sizeof(TCHAR));\r
 }\r
 \r
@@ -254,7 +279,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_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
+    popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
     cleanup_nssm_service(service);\r
     return 2;\r
   }\r
@@ -262,7 +287,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   /* Get executable name */\r
   if (! service->native) {\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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);\r
       return 3;\r
     }\r
 \r
@@ -275,7 +300,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
     /* 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
+        popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);\r
         return 4;\r
       }\r
     }\r
@@ -284,14 +309,14 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   /* 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
+      popup_message(window, 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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DESCRIPTION);\r
       return 5;\r
     }\r
   }\r
@@ -319,21 +344,21 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
     /* 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
+      popup_message(window, 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
+      popup_message(window, 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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_USERNAME);\r
       return 6;\r
     }\r
 \r
@@ -353,11 +378,11 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
 \r
       if (! orig_service || ! orig_service->username || ! str_equiv(service->username, orig_service->username) || service->passwordlen || passwordlen) {\r
         if (! service->passwordlen) {\r
-          popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
           return 6;\r
         }\r
         if (passwordlen != service->passwordlen) {\r
-          popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
           return 6;\r
         }\r
         service->passwordlen++;\r
@@ -368,7 +393,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
           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
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password confirmation"), _T("install()"));\r
           return 6;\r
         }\r
 \r
@@ -379,7 +404,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
           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
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password"), _T("install()"));\r
           return 6;\r
         }\r
 \r
@@ -393,7 +418,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
           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
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_PASSWORD);\r
           return 6;\r
         }\r
 \r
@@ -408,13 +433,13 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
           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
+          popup_message(window, 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
+          popup_message(window, 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
@@ -433,6 +458,26 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   /* Remaining tabs are only for services we manage. */\r
   if (service->native) return 0;\r
 \r
+  /* Get process stuff. */\r
+  combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+  service->priority = priority_index_to_constant((unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0));\r
+\r
+  service->affinity = 0LL;\r
+  if (! (SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_GETCHECK, 0, 0) & BST_CHECKED)) {\r
+    HWND list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+    int selected = (int) SendMessage(list, LB_GETSELCOUNT, 0, 0);\r
+    int count = (int) SendMessage(list, LB_GETCOUNT, 0, 0);\r
+    if (! selected) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_WARN_AFFINITY_NONE);\r
+      return 5;\r
+    }\r
+    else if (selected < count) {\r
+      for (int i = 0; i < count; i++) {\r
+        if (SendMessage(list, LB_GETSEL, i, 0)) service->affinity |= (1LL << (__int64) i);\r
+      }\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
@@ -447,11 +492,12 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   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
+  check_number(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, &service->restart_delay);\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
+  check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);\r
+  check_io(window, _T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);\r
+  check_io(window, _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
@@ -462,6 +508,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   /* Get rotation stuff. */\r
   if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
     service->rotate_files = true;\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->rotate_stdout_online = service->rotate_stderr_online = true;
     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
@@ -471,47 +518,34 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   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
+      popup_message(window, 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
+      popup_message(window, 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
+    TCHAR *newenv;\r
+    unsigned long newlen;\r
+    if (unformat_environment(env, envlen, &newenv, &newlen)) {\r
       HeapFree(GetProcessHeap(), 0, env);\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));\r
+      popup_message(window, 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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);\r
       HeapFree(GetProcessHeap(), 0, env);\r
       cleanup_nssm_service(service);\r
       return 5;\r
@@ -543,37 +577,37 @@ int install(HWND window) {
   /* 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
+      popup_message(window, 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
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
       cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
       cleanup_nssm_service(service);\r
       return 3;\r
 \r
     case 4:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
       cleanup_nssm_service(service);\r
       return 4;\r
 \r
     case 5:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INSTALL_SERVICE_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INSTALL_SERVICE_FAILED);\r
       cleanup_nssm_service(service);\r
       return 5;\r
 \r
     case 6:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_CREATE_PARAMETERS_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_CREATE_PARAMETERS_FAILED);\r
       cleanup_nssm_service(service);\r
       return 6;\r
   }\r
 \r
-  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
   cleanup_nssm_service(service);\r
   return 0;\r
 }\r
@@ -587,13 +621,13 @@ int remove(HWND window) {
   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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
       cleanup_nssm_service(service);\r
       return 2;\r
     }\r
 \r
     /* Confirm */\r
-    if (popup_message(MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) {\r
+    if (popup_message(window, MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) {\r
       cleanup_nssm_service(service);\r
       return 0;\r
     }\r
@@ -601,27 +635,27 @@ int remove(HWND window) {
 \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
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("remove()"));\r
       cleanup_nssm_service(service);\r
       return 1;\r
 \r
     case 2:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
       cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_SERVICE_NOT_INSTALLED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_SERVICE_NOT_INSTALLED);\r
       return 3;\r
       cleanup_nssm_service(service);\r
 \r
     case 4:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_REMOVE_SERVICE_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_REMOVE_SERVICE_FAILED);\r
       cleanup_nssm_service(service);\r
       return 4;\r
   }\r
 \r
-  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
   cleanup_nssm_service(service);\r
   return 0;\r
 }\r
@@ -637,28 +671,28 @@ int edit(HWND window, nssm_service_t *orig_service) {
 \r
   switch (edit_service(service, true)) {\r
     case 1:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("edit()"));\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("edit()"));\r
       cleanup_nssm_service(service);\r
       return 1;\r
 \r
     case 3:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
       cleanup_nssm_service(service);\r
       return 3;\r
 \r
     case 4:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
       cleanup_nssm_service(service);\r
       return 4;\r
 \r
     case 5:\r
     case 6:\r
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_EDIT_PARAMETERS_FAILED);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_EDIT_PARAMETERS_FAILED);\r
       cleanup_nssm_service(service);\r
       return 6;\r
   }\r
 \r
-  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_EDITED, service->name);\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_EDITED, service->name);\r
   cleanup_nssm_service(service);\r
   return 0;\r
 }\r
@@ -774,6 +808,13 @@ INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {
           set_logon_enabled(1);\r
           break;\r
 \r
+        /* Affinity. */\r
+        case IDC_AFFINITY_ALL:\r
+          if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 0;\r
+          else enabled = 1;\r
+          set_affinity_enabled(enabled);\r
+          break;\r
+\r
         /* Shutdown methods. */\r
         case IDC_METHOD_CONSOLE:\r
           set_timeout_enabled(LOWORD(w), IDC_KILL_CONSOLE);\r
@@ -840,6 +881,8 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
 \r
       HWND tabs;\r
       HWND combo;\r
+      HWND list;\r
+      int i, n;\r
       tabs = GetDlgItem(window, IDC_TAB1);\r
       if (! tabs) return 0;\r
 \r
@@ -892,6 +935,54 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       /* Remaining tabs are only for services we manage. */\r
       if (service->native) return 1;\r
 \r
+      /* Process tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_PROCESS);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_PROCESS, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_PROCESS] = dialog(MAKEINTRESOURCE(IDD_PROCESS), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_PROCESS], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_REALTIME_PRIORITY, (LPARAM) message_string(NSSM_GUI_REALTIME_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_HIGH_PRIORITY, (LPARAM) message_string(NSSM_GUI_HIGH_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_ABOVE_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_ABOVE_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_BELOW_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_BELOW_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_IDLE_PRIORITY, (LPARAM) message_string(NSSM_GUI_IDLE_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_NORMAL_PRIORITY, 0);\r
+\r
+      list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+      n = num_cpus();\r
+      SendMessage(list, LB_SETCOLUMNWIDTH, 16, 0);\r
+      for (i = 0; i < n; i++) {\r
+        TCHAR buffer[3];\r
+        _sntprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("%d"), i);\r
+        SendMessage(list, LB_ADDSTRING, 0, (LPARAM) buffer);\r
+      }\r
+\r
+      /*\r
+        Size to fit.\r
+        The box is high enough for four rows.  It is wide enough for eight\r
+        columns without scrolling.  With scrollbars it shrinks to two rows.\r
+        Note that the above only holds if we set the column width BEFORE
+        adding the strings.
+      */\r
+      if (n < 32) {\r
+        int columns = (n - 1) / 4;\r
+        RECT rect;\r
+        GetWindowRect(list, &rect);\r
+        int width = rect.right - rect.left;
+        width -= (7 - columns) * 16;\r
+        int height = rect.bottom - rect.top;\r
+        if (n < 4) height -= (int) SendMessage(list, LB_GETITEMHEIGHT, 0, 0) * (4 - n);
+        SetWindowPos(list, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER);\r
+      }\r
+      SendMessage(list, LB_SELITEMRANGE, 1, MAKELPARAM(0, n));\r
+\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_CHECKED, 0);\r
+      set_affinity_enabled(0);\r
+\r
       /* Shutdown tab. */\r
       tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
       tab.cchTextMax = (int) _tcslen(tab.pszText);\r
@@ -923,6 +1014,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       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
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, 0, 0);\r
 \r
       /* I/O tab. */\r
       tab.pszText = message_string(NSSM_GUI_TAB_IO);\r
@@ -939,6 +1031,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE);\r
 \r
       /* Set defaults. */\r
+      SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_UNCHECKED, 0);\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