diff --git a/examples/desktop-server/Makefile b/examples/desktop-server/Makefile index 957f9007..e9cb0f2e 100644 --- a/examples/desktop-server/Makefile +++ b/examples/desktop-server/Makefile @@ -2,8 +2,8 @@ PROG ?= example ROOT ?= $(realpath $(CURDIR)/../..) DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_DIRECTORY_LISTING=1 -DMG_ENABLE_SSI=1 CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA) -VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) -VC98 = docker run --rm -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR) docker.io/mdashnet/vc98 +VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) /link /incremental:no /machine:IX86 +VC98 = docker run --rm -e Tmp=. -v $(ROOT):$(ROOT) -w $(CURDIR) docker.io/mdashnet/vc98 GCC = docker run --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2 all: $(PROG) @@ -16,10 +16,11 @@ mongoose_mac: $(PROG) cp $(PROG) $@ mongoose_linux: main.c - $(GCC) $(CC) ../../mongoose.c main.c $(CFLAGS) -o $@ + $(GCC) $(CC) ../../mongoose.c main.c $(CFLAGS) -s -static -o $@ mongoose.exe: main.c - $(VC98) wine cl ../../mongoose.c main.c $(VCFLAGS) ws2_32.lib /Fe$@ + $(VC98) wine rc /fomongoose.res win32/res.rc + $(VC98) wine cl ../../mongoose.c main.c $(VCFLAGS) mongoose.res shell32.lib ws2_32.lib user32.lib /out:$@ zip: mongoose.exe mongoose_linux mongoose_mac rm -rf mongoose; mkdir mongoose diff --git a/examples/desktop-server/main.c b/examples/desktop-server/main.c index fdcfa23f..4b6b3f3e 100644 --- a/examples/desktop-server/main.c +++ b/examples/desktop-server/main.c @@ -9,6 +9,9 @@ static const char *s_listening_address = "http://localhost:8000"; static const char *s_enable_hexdump = "no"; static const char *s_ssi_pattern = "#.shtml"; +static struct mg_mgr s_mgr; +static struct mg_timer s_timer; + static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_serve_opts opts = {s_root_dir, s_ssi_pattern}; @@ -50,17 +53,21 @@ static void cfg(const char *progname, const char *optname, const char *value) { LOG(LL_INFO, ("Setting option [%s] to [%s]", optname, value)); } -static void parse_config_file(const char *exe) { +static FILE *opencfg(const char *exe, const char *mode) { char path[1024]; const char *p = strrchr(exe, MG_DIRSEP); - FILE *fp; if (p == NULL) p = strrchr(exe, '/'); snprintf(path, sizeof(path), "%.*s%c%s", p == NULL ? 1 : (int) (p - exe), p == NULL ? "." : exe, MG_DIRSEP, "mongoose.conf"); - if ((fp = fopen(path, "rb")) != NULL) { + return fopen(path, mode); +} + +static void parse_config_file(const char *exe) { + FILE *fp = opencfg(exe, "rb"); + if (fp != NULL) { char line[8192], *name; int i, line_no = 0; - LOG(LL_INFO, ("Processing config file %s", path)); + LOG(LL_INFO, ("Processing config file...")); while (fgets(line, sizeof(line), fp) != NULL) { line_no++; // Trim whitespaces at the end @@ -71,20 +78,41 @@ static void parse_config_file(const char *exe) { name = &line[i]; while (line[i] != '\0' && !isspace(line[i])) i++; if (line[i] != ' ') { - LOG(LL_ERROR, - ("%s: ignoring invalid line %d: %s", path, line_no, line)); + LOG(LL_ERROR, ("Ignoring invalid line %d: %s", line_no, line)); } else { line[i++] = '\0'; // nul-terminate name while (isspace(line[i])) i++; - cfg(exe, name, &line[i]); + cfg(exe, name, strdup(&line[i])); } } fclose(fp); } } -int main(int argc, char *argv[]) { - struct mg_mgr mgr; +static void ccb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = ev_data; + LOG(LL_DEBUG, ("New version: %.*s", (int) hm->body.len, hm->body.ptr)); + c->is_closing = 1; + } + (void) fn_data; +} + +// Check latest version, alert user if upgrade is available +static void timer_fn(void *arg) { + struct mg_connection *c = + mg_http_connect(&s_mgr, "http://cesanta.com", ccb, NULL); + if (c) { + c->is_hexdumping = 1; + mg_printf(c, "%s", + "GET /downloads/mws/version.json HTTP/1.0\r\n" + "Host: cesanta.com\r\n" + "\r\n"); + } + (void) arg; +} + +static void init_mongoose(int argc, char *argv[]) { struct mg_connection *c; int i; @@ -94,18 +122,28 @@ int main(int argc, char *argv[]) { // Initialise stuff mg_log_set(s_debug_level); - mg_mgr_init(&mgr); - if ((c = mg_http_listen(&mgr, s_listening_address, cb, &mgr)) == NULL) { + mg_mgr_init(&s_mgr); + if ((c = mg_http_listen(&s_mgr, s_listening_address, cb, &s_mgr)) == NULL) { LOG(LL_ERROR, ("Cannot listen on %s. Use http://ADDR:PORT or :PORT", s_listening_address)); exit(EXIT_FAILURE); } + mg_timer_init(&s_timer, 3600000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, + &s_mgr); if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1; + s_root_dir = realpath(s_root_dir, NULL); + if (s_root_dir == NULL) s_root_dir = "."; + LOG(LL_INFO, ("Starting Mongoose v%s, serving %s", MG_VERSION, s_root_dir)); +} - // Start infinite event loop - LOG(LL_INFO, ("Starting Mongoose v%s, serving %s", MG_VERSION, - realpath(s_root_dir, NULL))); - for (;;) mg_mgr_poll(&mgr, 1000); - mg_mgr_free(&mgr); +#ifdef _WIN32 +#include "win32/winmain.c" +#else +int main(int argc, char *argv[]) { + init_mongoose(argc, argv); + for (;;) mg_mgr_poll(&s_mgr, 1000); + mg_timer_free(&s_timer); + mg_mgr_free(&s_mgr); return 0; } +#endif diff --git a/examples/desktop-server/win32/res.rc b/examples/desktop-server/win32/res.rc new file mode 100644 index 00000000..52dba67b --- /dev/null +++ b/examples/desktop-server/win32/res.rc @@ -0,0 +1 @@ +150 ICON DISCARDABLE "systray.ico" diff --git a/examples/desktop-server/win32/systray.ico b/examples/desktop-server/win32/systray.ico new file mode 100644 index 00000000..2c7c97eb Binary files /dev/null and b/examples/desktop-server/win32/systray.ico differ diff --git a/examples/desktop-server/win32/winmain.c b/examples/desktop-server/win32/winmain.c new file mode 100644 index 00000000..4185ff70 --- /dev/null +++ b/examples/desktop-server/win32/winmain.c @@ -0,0 +1,131 @@ +#include +#include + +static const char *s_server_name = "Mongoose v" MG_VERSION; +static HANDLE hThread; // Serving thread +static NOTIFYICONDATA TrayIcon; + +enum { + ID_QUIT = 100, // Menu item IDs + ID_SEPARATOR, + ID_SET_DIR, + ID_ICON_MONGOOSE = 150, // Icons for mongoose menu +}; + +static const char *get_my_url(void) { + static char buf[100]; + char local_ip[50] = "", hostname[200]; + struct hostent *he; + if (gethostname(hostname, sizeof(hostname)) == 0 && + (he = gethostbyname(hostname)) != NULL) { + snprintf(local_ip, sizeof(local_ip), "%s", + inet_ntoa(*(struct in_addr *) he->h_addr_list[0])); + } + if (local_ip[0] == '\0') snprintf(local_ip, sizeof(local_ip), "127.0.0.1"); + snprintf(buf, sizeof(buf), "http://%s:%d", local_ip, + mg_url_port(s_listening_address)); + return buf; +} + +static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + POINT pt; + HMENU hMenu; + switch (msg) { + case WM_COMMAND: + switch (LOWORD(wParam)) { + case ID_QUIT: + Shell_NotifyIcon(NIM_DELETE, &TrayIcon); + PostQuitMessage(0); + return 0; + case ID_SET_DIR: { + char path[PATH_MAX] = ""; + BROWSEINFO bi; + memset(&bi, 0, sizeof(bi)); + bi.hwndOwner = hWnd; + bi.lpszTitle = "Choose root directory:"; + bi.ulFlags = BIF_RETURNONLYFSDIRS; + SHGetPathFromIDList(SHBrowseForFolder(&bi), path); + if (path[0] != '\0') { + FILE *fp = opencfg(__argv[0], "w"); + s_root_dir = strdup(path); + if (fp != NULL) { + fprintf(fp, "-d %s\n", path); + fclose(fp); + } + } + break; + } + } + break; + case WM_USER: + switch (lParam) { + case WM_RBUTTONUP: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: { + char buf[1024]; + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, s_server_name); + AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); + snprintf(buf, sizeof(buf), "Shared directory: %s", s_root_dir); + AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf); + snprintf(buf, sizeof(buf), "URL: %s", get_my_url()); + AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf); + AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, ""); + AppendMenu(hMenu, MF_STRING, ID_SET_DIR, + "Change shared directory ..."); + AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit"); + GetCursorPos(&pt); + SetForegroundWindow(hWnd); + TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL); + PostMessage(hWnd, WM_NULL, 0, 0); + DestroyMenu(hMenu); + break; + } + } + break; + case WM_CLOSE: + Shell_NotifyIcon(NIM_DELETE, &TrayIcon); + PostQuitMessage(0); + return 0; // We've just sent our own quit message, with proper hwnd. + default: + break; + } + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) { + WNDCLASS cls; + HWND hWnd; + MSG msg = {0}; + + init_mongoose(__argc, __argv); + memset(&cls, 0, sizeof(cls)); + cls.lpfnWndProc = (WNDPROC) WindowProc; + cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); + cls.lpszClassName = s_server_name; + RegisterClass(&cls); + hWnd = CreateWindow(cls.lpszClassName, s_server_name, WS_OVERLAPPEDWINDOW, 0, + 0, 0, 0, NULL, NULL, NULL, NULL); + ShowWindow(hWnd, SW_HIDE); + + TrayIcon.cbSize = sizeof(TrayIcon); + TrayIcon.uID = ID_ICON_MONGOOSE; + TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + TrayIcon.hIcon = + LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON_MONGOOSE)); + TrayIcon.hWnd = hWnd; + snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", s_server_name); + TrayIcon.uCallbackMessage = WM_USER; + Shell_NotifyIcon(NIM_ADD, &TrayIcon); + + while (msg.message != WM_QUIT) { + mg_mgr_poll(&s_mgr, 1); + if (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE) > 0) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + mg_mgr_free(&s_mgr); + return msg.wParam; +}