/* * Copyright (c) 2013-2022, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include <stdio.h> #include <string.h> #include <windows.h> #include <shellapi.h> #include "ClientContext.h" #include "Config.h" #include "NetDb.hpp" #include "RouterContext.h" #include "Transports.h" #include "Tunnel.h" #include "version.h" #include "resource.h" #include "Daemon.h" #include "Win32App.h" #include "Win32NetState.h" #define ID_ABOUT 2000 #define ID_EXIT 2001 #define ID_CONSOLE 2002 #define ID_APP 2003 #define ID_GRACEFUL_SHUTDOWN 2004 #define ID_STOP_GRACEFUL_SHUTDOWN 2005 #define ID_RELOAD 2006 #define ID_ACCEPT_TRANSIT 2007 #define ID_DECLINE_TRANSIT 2008 #define ID_DATADIR 2009 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) #define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 #define FRAME_UPDATE_TIMER 2101 #define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 namespace i2p { namespace win32 { DWORD g_GracefulShutdownEndtime = 0; bool g_isWinService; static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { HMENU hPopup = CreatePopupMenu(); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if(!i2p::context.AcceptsTunnels()) InsertMenu (hPopup, -1, i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, ID_ACCEPT_TRANSIT, "Accept &transit"); else InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); if (!i2p::util::DaemonWin32::Instance ().isGraceful) InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); else InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); POINT p; if (!curpos) { GetCursorPos (&p); curpos = &p; } WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); SendMessage (hWnd, WM_COMMAND, cmd, 0); DestroyMenu(hPopup); } static void AddTrayIcon (HWND hWnd, bool notify = false) { NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); strcpy (nid.szTip, "i2pd"); if (notify) strcpy (nid.szInfo, "i2pd is starting"); Shell_NotifyIcon(NIM_ADD, &nid ); } static void RemoveTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; Shell_NotifyIcon (NIM_DELETE, &nid); } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " days, "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; seconds -= num * 60; } s << seconds << " seconds\n"; } template <typename size> static void ShowTransfered (std::stringstream& s, size transfer) { auto bytes = transfer & 0x03ff; transfer >>= 10; auto kbytes = transfer & 0x03ff; transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; auto gbytes = transfer; if (gbytes) s << gbytes << " GB, "; if (mbytes) s << mbytes << " MB, "; if (kbytes) s << kbytes << " KB, "; s << bytes << " Bytes\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing) { switch (status) { case eRouterStatusOK: s << "OK"; break; case eRouterStatusFirewalled: s << "FW"; break; case eRouterStatusUnknown: s << "Unk"; break; case eRouterStatusProxy: s << "Proxy"; break; case eRouterStatusMesh: s << "Mesh"; break; default: s << "Unk"; }; if (testing) s << " (Test)"; if (i2p::context.GetError () != eRouterErrorNone) { switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: s << " - Clock skew"; break; case eRouterErrorOffline: s << " - Offline"; break; case eRouterErrorSymmetricNAT: s << " - Symmetric NAT"; break; default: ; } } } static void PrintMainWindowText (std::stringstream& s) { s << "\n"; s << "Status: "; ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting ()); if (i2p::context.SupportsV6 ()) { s << " / "; ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6 ()); } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); if (g_GracefulShutdownEndtime != 0) { DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); } else s << "\n"; s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); s << "\n"; s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; s << "Tunnels: "; s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; s << "\n"; } static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static UINT s_uTaskbarRestart; switch (uMsg) { case WM_CREATE: { s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); AddTrayIcon (hWnd, true); break; } case WM_CLOSE: { RemoveTrayIcon (hWnd); KillTimer (hWnd, FRAME_UPDATE_TIMER); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); PostQuitMessage (0); break; } case WM_COMMAND: { switch (LOWORD(wParam)) { case ID_ABOUT: { std::stringstream text; text << "Version: " << I2PD_VERSION << " " << CODENAME; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_EXIT: { PostMessage (hWnd, WM_CLOSE, 0, 0); return 0; } case ID_ACCEPT_TRANSIT: { i2p::context.SetAcceptsTunnels (true); std::stringstream text; text << "I2Pd now accept transit tunnels"; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_DECLINE_TRANSIT: { i2p::context.SetAcceptsTunnels (false); std::stringstream text; text << "I2Pd now decline new transit tunnels"; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (false); SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; i2p::util::DaemonWin32::Instance ().isGraceful = true; return 0; } case ID_STOP_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (true); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); g_GracefulShutdownEndtime = 0; i2p::util::DaemonWin32::Instance ().isGraceful = false; return 0; } case ID_RELOAD: { i2p::client::context.ReloadConfig(); std::stringstream text; text << "I2Pd reloading configs..."; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_CONSOLE: { char buf[30]; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); return 0; } case ID_APP: { ShowWindow(hWnd, SW_SHOW); SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } case ID_DATADIR: { std::string datadir(i2p::fs::GetUTF8DataDir()); ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); return 0; } } break; } case WM_SYSCOMMAND: { switch (wParam) { case SC_MINIMIZE: { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } case SC_CLOSE: { std::string close; i2p::config::GetOption("close", close); if (0 == close.compare("ask")) switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) { case IDYES: close = "minimize"; break; case IDNO: close = "exit"; break; default: return 0; } if (0 == close.compare("minimize")) { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } if (0 != close.compare("exit")) { ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); return 0; } } } #if (__cplusplus >= 201703L) // C++ 17 or higher [[fallthrough]]; #endif } case WM_TRAYICON: { switch (lParam) { case WM_LBUTTONUP: case WM_RBUTTONUP: { SetForegroundWindow (hWnd); ShowPopupMenu(hWnd, NULL, -1); PostMessage (hWnd, WM_APP + 1, 0, 0); break; } } break; } case WM_TIMER: { switch(wParam) { case IDT_GRACEFUL_SHUTDOWN_TIMER: { g_GracefulShutdownEndtime = 0; PostMessage (hWnd, WM_CLOSE, 0, 0); // exit return 0; } case IDT_GRACEFUL_TUNNELCHECK_TIMER: { if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) PostMessage (hWnd, WM_CLOSE, 0, 0); else SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); return 0; } case FRAME_UPDATE_TIMER: { InvalidateRect(hWnd, NULL, TRUE); return 0; } } break; } case WM_PAINT: { HDC hDC; PAINTSTRUCT ps; RECT rp; HFONT hFont; std::stringstream s; PrintMainWindowText (s); hDC = BeginPaint (hWnd, &ps); GetClientRect(hWnd, &rp); SetTextColor(hDC, 0x00D43B69); hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); SelectObject(hDC,hFont); DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); DeleteObject(hFont); EndPaint(hWnd, &ps); break; } default: { if (uMsg == s_uTaskbarRestart) AddTrayIcon (hWnd, false); break; } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } bool StartWin32App (bool isWinService) { g_isWinService = isWinService; if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); return false; } // register main window auto hInst = GetModuleHandle(NULL); WNDCLASSEX wclx; memset (&wclx, 0, sizeof(wclx)); wclx.cbSize = sizeof(wclx); wclx.style = 0; wclx.lpfnWndProc = WndProc; //wclx.cbClsExtra = 0; //wclx.cbWndExtra = 0; wclx.hInstance = hInst; wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wclx.lpszMenuName = NULL; wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } // COM requires message loop to work, which is not implemented in service mode if (!g_isWinService) SubscribeToEvents(); return true; } int RunWin32App () { MSG msg; while (GetMessage (&msg, NULL, 0, 0 )) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } void StopWin32App () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); else if(!g_isWinService) UnSubscribeFromEvents(); UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } bool GracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } bool StopGracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } } }