diff --git a/build/res.rc b/build/res.rc index f55c5382..38a80bb9 100644 --- a/build/res.rc +++ b/build/res.rc @@ -1 +1 @@ -200 ICON DISCARDABLE "systray.ico" +100 ICON DISCARDABLE "systray.ico" diff --git a/main.c b/main.c index 7abed9c1..963482b3 100644 --- a/main.c +++ b/main.c @@ -40,6 +40,7 @@ #ifdef _WIN32 #include #include +#include #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;