Browse Source

Added config editor for Win32

Sergey Lyubka 12 years ago
parent
commit
f0a2924a7c
2 changed files with 298 additions and 33 deletions
  1. 1 1
      build/res.rc
  2. 297 32
      main.c

+ 1 - 1
build/res.rc

@@ -1 +1 @@
-200 ICON DISCARDABLE "systray.ico"
+100 ICON DISCARDABLE "systray.ico"

+ 297 - 32
main.c

@@ -40,6 +40,7 @@
 #ifdef _WIN32
 #include <windows.h>
 #include <winsvc.h>
+#include <shlobj.h>
 #define PATH_MAX MAX_PATH
 #define S_ISDIR(x) ((x) & _S_IFDIR)
 #define DIRSEP '\\'
@@ -111,6 +112,14 @@ static void show_usage_and_exit(void) {
 }
 
 #if defined(_WIN32) || defined(USE_COCOA)
+static const char *config_file_top_comment =
+"# Mongoose web server configuration file.\n"
+"# For detailed description of every option, visit\n"
+"# https://github.com/valenok/mongoose/blob/master/UserManual.md\n"
+"# Lines starting with '#' and empty lines are ignored.\n"
+"# To make a change, remove leading '#', modify option's value,\n"
+"# save this file and then restart Mongoose.\n\n";
+
 static void create_config_file(const char *path) {
   const char **names, *value;
   FILE *fp;
@@ -120,13 +129,7 @@ static void create_config_file(const char *path) {
   if ((fp = fopen(path, "r")) != NULL) {
     fclose(fp);
   } else if ((fp = fopen(path, "a+")) != NULL) {
-    fprintf(fp, "%s",
-            "# Mongoose web server configuration file.\n"
-            "# For detailed description of every option, visit\n"
-            "# https://github.com/valenok/mongoose/blob/master/UserManual.md\n"
-            "# Lines starting with '#' and empty lines are ignored.\n"
-            "# To make a change, remove leading '#', modify option's value,\n"
-            "# save this file and then restart Mongoose.\n\n");
+    fprintf(fp, "%s", config_file_top_comment);
     names = mg_get_valid_option_names();
     for (i = 0; names[i] != NULL; i += 3) {
       value = mg_get_option(ctx, names[i]);
@@ -215,18 +218,20 @@ static void process_command_line_arguments(char *argv[], char **options) {
 
     // Loop over the lines in config file
     while (fgets(line, sizeof(line), fp) != NULL) {
-
       line_no++;
 
       // Ignore empty lines and comments
-	  for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
-      if (line[i] == '#' || line[i] == '\0')
+      for (i = 0; isspace(* (unsigned char *) &line[i]); ) i++;
+      if (line[i] == '#' || line[i] == '\0') {
         continue;
+      }
 
       if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
-        die("%s: line %d is invalid", config_file, (int) line_no);
+        printf("%s: line %d is invalid, ignoring it:\n %s",
+               config_file, (int) line_no, line);
+      } else {
+        set_option(options, opt, val);
       }
-      set_option(options, opt, val);
     }
 
     (void) fclose(fp);
@@ -293,9 +298,16 @@ static void start_mongoose(int argc, char *argv[]) {
 }
 
 #ifdef _WIN32
+enum {
+  ID_TRAYICON = 100, ID_QUIT, ID_SETTINGS, ID_SEPARATOR, ID_INSTALL_SERVICE,
+  ID_REMOVE_SERVICE, ID_STATIC, ID_GROUP, ID_SAVE, ID_TIMER, ID_RESET_DEFAULTS,
+  ID_STATUS, ID_CONTROLS = 200, ID_FILE_BUTTONS_DELTA = 1000
+};
+static HICON hIcon;
 static SERVICE_STATUS ss;
 static SERVICE_STATUS_HANDLE hStatus;
 static const char *service_magic_argument = "--";
+static NOTIFYICONDATA TrayIcon;
 
 static void WINAPI ControlHandler(DWORD code) {
   if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
@@ -323,21 +335,6 @@ static void WINAPI ServiceMain(void) {
   SetServiceStatus(hStatus, &ss);
 }
 
-#define ID_TRAYICON 100
-#define ID_QUIT 101
-#define ID_EDIT_CONFIG 102
-#define ID_SEPARATOR 103
-#define ID_INSTALL_SERVICE 104
-#define ID_REMOVE_SERVICE 105
-#define ID_ICON 200
-static NOTIFYICONDATA TrayIcon;
-
-static void edit_config_file(void) {
-  char cmd[200];
-  create_config_file(config_file);
-  snprintf(cmd, sizeof(cmd), "notepad.exe %s", config_file);
-  WinExec(cmd, SW_SHOW);
-}
 
 static void show_error(void) {
   char buf[256];
@@ -348,6 +345,273 @@ static void show_error(void) {
   MessageBox(NULL, buf, "Error", MB_OK);
 }
 
+static void *align(void *ptr, DWORD alig) {
+  ULONG ul = (ULONG) ptr;
+  ul += alig;
+  ul &= ~alig;
+  return ((void *) ul);
+}
+
+static int is_boolean_option(const char *option_name) {
+  return !strcmp(option_name, "enable_directory_listing") ||
+    !strcmp(option_name, "enable_keep_alive");
+}
+
+static int is_filename_option(const char *option_name) {
+  return !strcmp(option_name, "cgi_interpreter") ||
+    !strcmp(option_name, "global_auth_file") ||
+    !strcmp(option_name, "put_delete_auth_file") ||
+    !strcmp(option_name, "access_log_file") ||
+    !strcmp(option_name, "error_log_file") ||
+    !strcmp(option_name, "ssl_certificate");
+}
+
+static int is_directory_option(const char *option_name) {
+  return !strcmp(option_name, "document_root");
+}
+
+static int is_numeric_options(const char *option_name) {
+  return !strcmp(option_name, "num_threads");
+}
+
+static void save_config(HWND hDlg, FILE *fp) {
+  char value[2000];
+  const char **options, *name, *default_value;
+  int i, id;
+
+  fprintf(fp, "%s", config_file_top_comment);
+  options = mg_get_valid_option_names();
+  for (i = 0; options[i] != NULL; i += 3) {
+    name = options[i + 1];
+    id = ID_CONTROLS + i / 3;
+    if (is_boolean_option(name)) {
+      snprintf(value, sizeof(value), "%s",
+               IsDlgButtonChecked(hDlg, id) ? "yes" : "no");
+    } else {
+      GetDlgItemText(hDlg, id, value, sizeof(value));
+    }
+    default_value = options[i + 2] == NULL ? "" : options[i + 2];
+    // If value is the same as default, skip it
+    if (strcmp(value, default_value) != 0) {
+      fprintf(fp, "%s %s\n", name, value);
+    }
+  }
+}
+
+static BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lP) {
+  FILE *fp;
+  int i;
+  const char *name, *value, **options = mg_get_valid_option_names();
+
+  switch (msg) {
+    case WM_CLOSE:
+      KillTimer(hDlg, ID_TIMER);
+      DestroyWindow(hDlg);
+      break;
+
+    case WM_COMMAND:
+      switch (LOWORD(wParam)) {
+        case ID_SAVE:
+          EnableWindow(GetDlgItem(hDlg, ID_SAVE), FALSE);
+          if ((fp = fopen(config_file, "w+")) != NULL) {
+            save_config(hDlg, fp);
+            fclose(fp);
+            mg_stop(ctx);
+            start_mongoose(__argc, __argv);
+          }
+          EnableWindow(GetDlgItem(hDlg, ID_SAVE), TRUE);
+          break;
+        case ID_RESET_DEFAULTS:
+          for (i = 0; options[i] != NULL; i += 3) {
+            name = options[i + 1];
+            value = options[i + 2] == NULL ? "" : options[i + 2];
+            if (is_boolean_option(name)) {
+              CheckDlgButton(hDlg, ID_CONTROLS + i / 3, !strcmp(value, "yes") ?
+                             BST_CHECKED : BST_UNCHECKED);
+            } else {
+              SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i / 3), value);
+            }
+          }
+          break;
+      }
+
+      for (i = 0; options[i] != NULL; i += 3) {
+        name = options[i + 1];
+        if ((is_filename_option(name) || is_directory_option(name)) &&
+            LOWORD(wParam) == ID_CONTROLS + i / 3 + ID_FILE_BUTTONS_DELTA) {
+          OPENFILENAME of;
+          BROWSEINFO bi;
+          char path[PATH_MAX] = "";
+
+          memset(&of, 0, sizeof(of));
+          of.lStructSize = sizeof(of);
+          of.hwndOwner = (HWND) hDlg;
+          of.lpstrFile = path;
+          of.nMaxFile = sizeof(path);
+          of.lpstrInitialDir = mg_get_option(ctx, "document_root");
+          of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR;
+
+          memset(&bi, 0, sizeof(bi));
+          bi.hwndOwner = (HWND) hDlg;
+          bi.lpszTitle = "Choose WWW root directory:";
+          bi.ulFlags = BIF_RETURNONLYFSDIRS;
+
+          if (is_directory_option(name)) {
+            SHGetPathFromIDList(SHBrowseForFolder(&bi), path);
+          } else {
+            GetOpenFileName(&of);
+          }
+
+          if (path[0] != '\0') {
+            SetWindowText(GetDlgItem(hDlg, ID_CONTROLS + i / 3), path);
+          }
+        }
+      }
+
+      break;
+
+    case WM_INITDIALOG:
+      SendMessage(hDlg, WM_SETICON,(WPARAM) ICON_SMALL, (LPARAM) hIcon);
+      SendMessage(hDlg, WM_SETICON,(WPARAM) ICON_BIG, (LPARAM) hIcon);
+      SetWindowText(hDlg, "Mongoose settings");
+      SetFocus(GetDlgItem(hDlg, ID_SAVE));
+      for (i = 0; options[i] != NULL; i += 3) {
+        name = options[i + 1];
+        value = mg_get_option(ctx, name);
+        if (is_boolean_option(name)) {
+          CheckDlgButton(hDlg, ID_CONTROLS + i / 3, !strcmp(value, "yes") ?
+                         BST_CHECKED : BST_UNCHECKED);
+        } else {
+          SetDlgItemText(hDlg, ID_CONTROLS + i / 3, value == NULL ? "" : value);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
+  return FALSE;
+}
+
+static void add_control(unsigned char **mem, DLGTEMPLATE *dia, WORD type,
+                        DWORD id, DWORD style, WORD x, WORD y,
+                        WORD cx, WORD cy, const char *caption) {
+  DLGITEMTEMPLATE *tp;
+  LPWORD p;
+
+  dia->cdit++;
+
+  *mem = align(*mem, 3);
+  tp = (DLGITEMTEMPLATE *) *mem;
+
+  tp->id = (WORD)id;
+  tp->style = style;
+  tp->dwExtendedStyle = 0;
+  tp->x = x;
+  tp->y = y;
+  tp->cx = cx;
+  tp->cy = cy;
+
+  p = align(*mem + sizeof(*tp), 1);
+  *p++ = 0xffff;
+  *p++ = type;
+
+  while (*caption != '\0') {
+    *p++ = (WCHAR) *caption++;
+  }
+  *p++ = 0;
+  p = align(p, 1);
+
+  *p++ = 0;
+  *mem = (unsigned char *) p;
+}
+
+static void show_settings_dialog() {
+#define HEIGHT 15
+#define WIDTH 400
+#define LABEL_WIDTH 80
+
+  unsigned char mem[4096], *p;
+  const char **option_names, *long_option_name;
+  DWORD style;
+  DLGTEMPLATE *dia = (DLGTEMPLATE *) mem;
+  WORD i, cl, x, y, width, nelems = 0;
+  static int guard;
+
+  static struct {
+    DLGTEMPLATE template; // 18 bytes
+    WORD menu, class;
+    wchar_t caption[1];
+    WORD fontsiz;
+    wchar_t fontface[7];
+  } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
+    DS_SETFONT | WS_DLGFRAME, WS_EX_TOOLWINDOW, 0, 200, 200, WIDTH, 0},
+    0, 0, L"", 8, L"Tahoma"};
+
+  if (guard == 0) {
+    guard++;
+  } else {
+    return;
+  }
+
+  (void) memset(mem, 0, sizeof(mem));
+  (void) memcpy(mem, &dialog_header, sizeof(dialog_header));
+  p = mem + sizeof(dialog_header);
+
+  option_names = mg_get_valid_option_names();
+  for (i = 0; option_names[i] != NULL; i += 3) {
+    long_option_name = option_names[i + 1];
+    style = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
+    x = 10 + (WIDTH / 2) * (nelems % 2);
+    y = (nelems/2 + 1) * HEIGHT + 5;
+    width = WIDTH / 2 - 20 - LABEL_WIDTH;
+    if (is_numeric_options(long_option_name)) {
+      style |= ES_NUMBER;
+      cl = 0x81;
+      style |= WS_BORDER | ES_AUTOHSCROLL;
+    } else if (is_boolean_option(long_option_name)) {
+      cl = 0x80;
+      style |= BS_AUTOCHECKBOX;
+    } else if (is_filename_option(long_option_name) ||
+               is_directory_option(long_option_name)) {
+      style |= WS_BORDER | ES_AUTOHSCROLL;
+      width -= 20;
+      cl = 0x81;
+      add_control(&p, dia, 0x80,
+                  ID_CONTROLS + (i / 3) + ID_FILE_BUTTONS_DELTA,
+                  WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
+                  (WORD) (x + width + LABEL_WIDTH + 5),
+                  y, 15, 12, "...");
+    } else {
+      cl = 0x81;
+      style |= WS_BORDER | ES_AUTOHSCROLL;
+    }
+    add_control(&p, dia, 0x82, ID_STATIC, WS_VISIBLE | WS_CHILD,
+                x, y, LABEL_WIDTH, HEIGHT, long_option_name);
+    add_control(&p, dia, cl, ID_CONTROLS + (i / 3), style,
+                (WORD) (x + LABEL_WIDTH), y, width, 12, "");
+    nelems++;
+  }
+
+  y = (WORD) (((nelems + 1) / 2 + 1) * HEIGHT + 5);
+  add_control(&p, dia, 0x80, ID_GROUP, WS_CHILD | WS_VISIBLE |
+              BS_GROUPBOX, 5, 5, WIDTH - 10, y, " Settings ");
+  y += 10;
+  add_control(&p, dia, 0x80, ID_SAVE,
+              WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
+              WIDTH - 70, y, 65, 12, "Save Settings");
+  add_control(&p, dia, 0x80, ID_RESET_DEFAULTS,
+              WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
+              WIDTH - 140, y, 65, 12, "Reset to defaults");
+  add_control(&p, dia, 0x82, ID_STATIC,
+              WS_CHILD | WS_VISIBLE | WS_DISABLED,
+              5, y, 180, 12, server_name);
+
+  dia->cy = ((nelems + 1) / 2 + 1) * HEIGHT + 30;
+  DialogBoxIndirectParam(NULL, dia, NULL, DlgProc, (LPARAM) NULL);
+  guard--;
+}
+
 static int manage_service(int action) {
   static const char *service_name = "Mongoose";
   SC_HANDLE hSCM = NULL, hService = NULL;
@@ -417,8 +681,8 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
           Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
           PostQuitMessage(0);
           return 0;
-        case ID_EDIT_CONFIG:
-          edit_config_file();
+        case ID_SETTINGS:
+          show_settings_dialog();
           break;
         case ID_INSTALL_SERVICE:
         case ID_REMOVE_SERVICE:
@@ -443,7 +707,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
           AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
                      ID_REMOVE_SERVICE, "Deinstall service");
           AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
-          AppendMenu(hMenu, MF_STRING, ID_EDIT_CONFIG, "Edit config file");
+          AppendMenu(hMenu, MF_STRING, ID_SETTINGS, "Settings");
           AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
           GetCursorPos(&pt);
           SetForegroundWindow(hWnd);
@@ -482,8 +746,9 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
   TrayIcon.cbSize = sizeof(TrayIcon);
   TrayIcon.uID = ID_TRAYICON;
   TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
-  TrayIcon.hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON),
-                             IMAGE_ICON, 16, 16, 0);
+  TrayIcon.hIcon = hIcon = LoadImage(GetModuleHandle(NULL),
+                                     MAKEINTRESOURCE(ID_TRAYICON),
+                                     IMAGE_ICON, 16, 16, 0);
   TrayIcon.hWnd = hWnd;
   snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
   TrayIcon.uCallbackMessage = WM_USER;