From 83c106a857e37010984110293a6f92bacf04939f Mon Sep 17 00:00:00 2001 From: cpq Date: Wed, 13 Jan 2021 03:07:51 +0000 Subject: [PATCH] UI for Win32 --- examples/desktop-server/Makefile | 9 +- examples/desktop-server/main.c | 70 +++++++++--- examples/desktop-server/win32/res.rc | 1 + examples/desktop-server/win32/systray.ico | Bin 0 -> 15086 bytes examples/desktop-server/win32/winmain.c | 131 ++++++++++++++++++++++ 5 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 examples/desktop-server/win32/res.rc create mode 100644 examples/desktop-server/win32/systray.ico create mode 100644 examples/desktop-server/win32/winmain.c 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 0000000000000000000000000000000000000000..2c7c97eb0b44734abe1d8aade4d8f2e0a28c6177 GIT binary patch literal 15086 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 J17Wt`{|_E@fYty2 literal 0 HcmV?d00001 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; +}