1 /* 2 * Copyright (c) 2013-2020, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <stdio.h> 10 #include <string.h> 11 #include <windows.h> 12 #include <shellapi.h> 13 #include "ClientContext.h" 14 #include "Config.h" 15 #include "NetDb.hpp" 16 #include "RouterContext.h" 17 #include "Transports.h" 18 #include "Tunnel.h" 19 #include "version.h" 20 #include "resource.h" 21 #include "Daemon.h" 22 #include "Win32App.h" 23 #include "Win32NetState.h" 24 25 #define ID_ABOUT 2000 26 #define ID_EXIT 2001 27 #define ID_CONSOLE 2002 28 #define ID_APP 2003 29 #define ID_GRACEFUL_SHUTDOWN 2004 30 #define ID_STOP_GRACEFUL_SHUTDOWN 2005 31 #define ID_RELOAD 2006 32 #define ID_ACCEPT_TRANSIT 2007 33 #define ID_DECLINE_TRANSIT 2008 34 #define ID_DATADIR 2009 35 36 #define ID_TRAY_ICON 2050 37 #define WM_TRAYICON (WM_USER + 1) 38 39 #define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 40 #define FRAME_UPDATE_TIMER 2101 41 #define IDT_GRACEFUL_TUNNELCHECK_TIMER 2102 42 43 namespace i2p 44 { 45 namespace win32 46 { 47 DWORD g_GracefulShutdownEndtime = 0; 48 ShowPopupMenu(HWND hWnd,POINT * curpos,int wDefaultItem)49 static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) 50 { 51 HMENU hPopup = CreatePopupMenu(); 52 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); 53 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); 54 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); 55 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); 56 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); 57 if(!i2p::context.AcceptsTunnels()) 58 InsertMenu (hPopup, -1, 59 i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, 60 ID_ACCEPT_TRANSIT, "Accept &transit"); 61 else 62 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); 63 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); 64 if (!i2p::util::DaemonWin32::Instance ().isGraceful) 65 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); 66 else 67 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); 68 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); 69 SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); 70 SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); 71 72 POINT p; 73 if (!curpos) 74 { 75 GetCursorPos (&p); 76 curpos = &p; 77 } 78 79 WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); 80 SendMessage (hWnd, WM_COMMAND, cmd, 0); 81 82 DestroyMenu(hPopup); 83 } 84 AddTrayIcon(HWND hWnd,bool notify=false)85 static void AddTrayIcon (HWND hWnd, bool notify = false) 86 { 87 NOTIFYICONDATA nid; 88 memset(&nid, 0, sizeof(nid)); 89 nid.cbSize = sizeof(nid); 90 nid.hWnd = hWnd; 91 nid.uID = ID_TRAY_ICON; 92 nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; 93 nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; 94 nid.uCallbackMessage = WM_TRAYICON; 95 nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); 96 strcpy (nid.szTip, "i2pd"); 97 if (notify) strcpy (nid.szInfo, "i2pd is starting"); 98 Shell_NotifyIcon(NIM_ADD, &nid ); 99 } 100 RemoveTrayIcon(HWND hWnd)101 static void RemoveTrayIcon (HWND hWnd) 102 { 103 NOTIFYICONDATA nid; 104 nid.hWnd = hWnd; 105 nid.uID = ID_TRAY_ICON; 106 Shell_NotifyIcon (NIM_DELETE, &nid); 107 } 108 ShowUptime(std::stringstream & s,int seconds)109 static void ShowUptime (std::stringstream& s, int seconds) 110 { 111 int num; 112 113 if ((num = seconds / 86400) > 0) { 114 s << num << " days, "; 115 seconds -= num * 86400; 116 } 117 if ((num = seconds / 3600) > 0) { 118 s << num << " hours, "; 119 seconds -= num * 3600; 120 } 121 if ((num = seconds / 60) > 0) { 122 s << num << " min, "; 123 seconds -= num * 60; 124 } 125 s << seconds << " seconds\n"; 126 } 127 ShowTransfered(std::stringstream & s,size transfer)128 template <typename size> static void ShowTransfered (std::stringstream& s, size transfer) 129 { 130 auto bytes = transfer & 0x03ff; 131 transfer >>= 10; 132 auto kbytes = transfer & 0x03ff; 133 transfer >>= 10; 134 auto mbytes = transfer & 0x03ff; 135 transfer >>= 10; 136 auto gbytes = transfer; 137 138 if (gbytes) 139 s << gbytes << " GB, "; 140 if (mbytes) 141 s << mbytes << " MB, "; 142 if (kbytes) 143 s << kbytes << " KB, "; 144 s << bytes << " Bytes\n"; 145 } 146 ShowNetworkStatus(std::stringstream & s,RouterStatus status)147 static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) 148 { 149 switch (status) 150 { 151 case eRouterStatusOK: s << "OK"; break; 152 case eRouterStatusTesting: s << "Test"; break; 153 case eRouterStatusFirewalled: s << "FW"; break; 154 case eRouterStatusUnknown: s << "Unk"; break; 155 case eRouterStatusProxy: s << "Proxy"; break; 156 case eRouterStatusMesh: s << "Mesh"; break; 157 case eRouterStatusError: 158 { 159 s << "Err"; 160 switch (i2p::context.GetError ()) 161 { 162 case eRouterErrorClockSkew: 163 s << " - Clock skew"; 164 break; 165 case eRouterErrorOffline: 166 s << " - Offline"; 167 break; 168 case eRouterErrorSymmetricNAT: 169 s << " - Symmetric NAT"; 170 break; 171 default: ; 172 } 173 break; 174 } 175 default: s << "Unk"; 176 } 177 } 178 PrintMainWindowText(std::stringstream & s)179 static void PrintMainWindowText (std::stringstream& s) 180 { 181 s << "\n"; 182 s << "Status: "; 183 ShowNetworkStatus (s, i2p::context.GetStatus ()); 184 if (i2p::context.SupportsV6 ()) 185 { 186 s << " / "; 187 ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); 188 } 189 s << "; "; 190 s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; 191 s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); 192 if (g_GracefulShutdownEndtime != 0) 193 { 194 DWORD GracefulTimeLeft = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; 195 s << "Graceful shutdown, time left: "; ShowUptime(s, GracefulTimeLeft); 196 } 197 else 198 s << "\n"; 199 s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; 200 s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; 201 s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); 202 s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); 203 s << "\n"; 204 s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; 205 s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; 206 s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; 207 s << "Tunnels: "; 208 s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; 209 s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; 210 s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; 211 s << "\n"; 212 } 213 WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)214 static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 215 { 216 static UINT s_uTaskbarRestart; 217 218 switch (uMsg) 219 { 220 case WM_CREATE: 221 { 222 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); 223 AddTrayIcon (hWnd, true); 224 break; 225 } 226 case WM_CLOSE: 227 { 228 RemoveTrayIcon (hWnd); 229 KillTimer (hWnd, FRAME_UPDATE_TIMER); 230 KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); 231 KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); 232 PostQuitMessage (0); 233 break; 234 } 235 case WM_COMMAND: 236 { 237 switch (LOWORD(wParam)) 238 { 239 case ID_ABOUT: 240 { 241 std::stringstream text; 242 text << "Version: " << I2PD_VERSION << " " << CODENAME; 243 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 244 return 0; 245 } 246 case ID_EXIT: 247 { 248 PostMessage (hWnd, WM_CLOSE, 0, 0); 249 return 0; 250 } 251 case ID_ACCEPT_TRANSIT: 252 { 253 i2p::context.SetAcceptsTunnels (true); 254 std::stringstream text; 255 text << "I2Pd now accept transit tunnels"; 256 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 257 return 0; 258 } 259 case ID_DECLINE_TRANSIT: 260 { 261 i2p::context.SetAcceptsTunnels (false); 262 std::stringstream text; 263 text << "I2Pd now decline new transit tunnels"; 264 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 265 return 0; 266 } 267 case ID_GRACEFUL_SHUTDOWN: 268 { 269 i2p::context.SetAcceptsTunnels (false); 270 SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes 271 SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second 272 g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; 273 i2p::util::DaemonWin32::Instance ().isGraceful = true; 274 return 0; 275 } 276 case ID_STOP_GRACEFUL_SHUTDOWN: 277 { 278 i2p::context.SetAcceptsTunnels (true); 279 KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); 280 KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); 281 g_GracefulShutdownEndtime = 0; 282 i2p::util::DaemonWin32::Instance ().isGraceful = false; 283 return 0; 284 } 285 case ID_RELOAD: 286 { 287 i2p::client::context.ReloadConfig(); 288 std::stringstream text; 289 text << "I2Pd reloading configs..."; 290 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 291 return 0; 292 } 293 case ID_CONSOLE: 294 { 295 char buf[30]; 296 std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); 297 uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); 298 snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); 299 ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); 300 return 0; 301 } 302 case ID_APP: 303 { 304 ShowWindow(hWnd, SW_SHOW); 305 SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); 306 return 0; 307 } 308 case ID_DATADIR: 309 { 310 std::string datadir(i2p::fs::GetUTF8DataDir()); 311 ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); 312 return 0; 313 } 314 } 315 break; 316 } 317 case WM_SYSCOMMAND: 318 { 319 switch (wParam) 320 { 321 case SC_MINIMIZE: 322 { 323 ShowWindow(hWnd, SW_HIDE); 324 KillTimer (hWnd, FRAME_UPDATE_TIMER); 325 return 0; 326 } 327 case SC_CLOSE: 328 { 329 std::string close; i2p::config::GetOption("close", close); 330 if (0 == close.compare("ask")) 331 switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" 332 " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", 333 "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) 334 { 335 case IDYES: close = "minimize"; break; 336 case IDNO: close = "exit"; break; 337 default: return 0; 338 } 339 if (0 == close.compare("minimize")) 340 { 341 ShowWindow(hWnd, SW_HIDE); 342 KillTimer (hWnd, FRAME_UPDATE_TIMER); 343 return 0; 344 } 345 if (0 != close.compare("exit")) 346 { 347 ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); 348 return 0; 349 } 350 } 351 } 352 } 353 case WM_TRAYICON: 354 { 355 switch (lParam) 356 { 357 case WM_LBUTTONUP: 358 case WM_RBUTTONUP: 359 { 360 SetForegroundWindow (hWnd); 361 ShowPopupMenu(hWnd, NULL, -1); 362 PostMessage (hWnd, WM_APP + 1, 0, 0); 363 break; 364 } 365 } 366 break; 367 } 368 case WM_TIMER: 369 { 370 switch(wParam) 371 { 372 case IDT_GRACEFUL_SHUTDOWN_TIMER: 373 { 374 g_GracefulShutdownEndtime = 0; 375 PostMessage (hWnd, WM_CLOSE, 0, 0); // exit 376 return 0; 377 } 378 case IDT_GRACEFUL_TUNNELCHECK_TIMER: 379 { 380 if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) 381 PostMessage (hWnd, WM_CLOSE, 0, 0); 382 else 383 SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); 384 return 0; 385 } 386 case FRAME_UPDATE_TIMER: 387 { 388 InvalidateRect(hWnd, NULL, TRUE); 389 return 0; 390 } 391 } 392 break; 393 } 394 case WM_PAINT: 395 { 396 HDC hDC; 397 PAINTSTRUCT ps; 398 RECT rp; 399 HFONT hFont; 400 std::stringstream s; PrintMainWindowText (s); 401 hDC = BeginPaint (hWnd, &ps); 402 GetClientRect(hWnd, &rp); 403 SetTextColor(hDC, 0x00D43B69); 404 hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); 405 SelectObject(hDC,hFont); 406 DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); 407 DeleteObject(hFont); 408 EndPaint(hWnd, &ps); 409 break; 410 } 411 default: 412 { 413 if (uMsg == s_uTaskbarRestart) 414 AddTrayIcon (hWnd, false); 415 break; 416 } 417 } 418 return DefWindowProc( hWnd, uMsg, wParam, lParam); 419 } 420 StartWin32App()421 bool StartWin32App () 422 { 423 if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) 424 { 425 MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); 426 return false; 427 } 428 // register main window 429 auto hInst = GetModuleHandle(NULL); 430 WNDCLASSEX wclx; 431 memset (&wclx, 0, sizeof(wclx)); 432 wclx.cbSize = sizeof(wclx); 433 wclx.style = 0; 434 wclx.lpfnWndProc = WndProc; 435 //wclx.cbClsExtra = 0; 436 //wclx.cbWndExtra = 0; 437 wclx.hInstance = hInst; 438 wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); 439 wclx.hCursor = LoadCursor (NULL, IDC_ARROW); 440 //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); 441 wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 442 wclx.lpszMenuName = NULL; 443 wclx.lpszClassName = I2PD_WIN32_CLASSNAME; 444 RegisterClassEx (&wclx); 445 // create new window 446 if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) 447 { 448 MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); 449 return false; 450 } 451 SubscribeToEvents(); 452 return true; 453 } 454 RunWin32App()455 int RunWin32App () 456 { 457 MSG msg; 458 while (GetMessage (&msg, NULL, 0, 0 )) 459 { 460 TranslateMessage (&msg); 461 DispatchMessage (&msg); 462 } 463 return msg.wParam; 464 } 465 StopWin32App()466 void StopWin32App () 467 { 468 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 469 if (hWnd) 470 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); 471 // UnSubscribeFromEvents(); // TODO: understand why unsubscribing crashes app 472 UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); 473 } 474 GracefulShutdown()475 bool GracefulShutdown () 476 { 477 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 478 if (hWnd) 479 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); 480 return hWnd; 481 } 482 StopGracefulShutdown()483 bool StopGracefulShutdown () 484 { 485 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 486 if (hWnd) 487 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); 488 return hWnd; 489 } 490 } 491 } 492