Win32App.cpp
1 /* 2 * Copyright (c) 2013-2025, 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 static DWORD g_GracefulShutdownEndtime = 0; 48 bool g_isWinService; 49 50 static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) 51 { 52 HMENU hPopup = CreatePopupMenu(); 53 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); 54 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DATADIR, "Open &datadir"); 55 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "&Show app"); 56 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); 57 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); 58 if(!i2p::context.AcceptsTunnels()) 59 InsertMenu (hPopup, -1, 60 i2p::util::DaemonWin32::Instance ().isGraceful ? MF_BYPOSITION | MF_STRING | MF_GRAYED : MF_BYPOSITION | MF_STRING, 61 ID_ACCEPT_TRANSIT, "Accept &transit"); 62 else 63 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_DECLINE_TRANSIT, "Decline &transit"); 64 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload tunnels config"); 65 if (!i2p::util::DaemonWin32::Instance ().isGraceful) 66 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); 67 else 68 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "Stop &graceful shutdown"); 69 InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); 70 SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); 71 SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); 72 73 POINT p; 74 if (!curpos) 75 { 76 GetCursorPos (&p); 77 curpos = &p; 78 } 79 80 WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); 81 SendMessage (hWnd, WM_COMMAND, cmd, 0); 82 83 DestroyMenu(hPopup); 84 } 85 86 static void AddTrayIcon (HWND hWnd, bool notify = false) 87 { 88 NOTIFYICONDATA nid; 89 memset(&nid, 0, sizeof(nid)); 90 nid.cbSize = sizeof(nid); 91 nid.hWnd = hWnd; 92 nid.uID = ID_TRAY_ICON; 93 nid.uFlags = notify ? NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO : NIF_ICON | NIF_MESSAGE | NIF_TIP; 94 nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; 95 nid.uCallbackMessage = WM_TRAYICON; 96 nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); 97 strcpy (nid.szTip, "i2pd"); 98 if (notify) strcpy (nid.szInfo, "i2pd is starting"); 99 Shell_NotifyIcon(NIM_ADD, &nid ); 100 } 101 102 static void RemoveTrayIcon (HWND hWnd) 103 { 104 NOTIFYICONDATA nid; 105 nid.hWnd = hWnd; 106 nid.uID = ID_TRAY_ICON; 107 Shell_NotifyIcon (NIM_DELETE, &nid); 108 } 109 110 static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 111 { 112 static UINT s_uTaskbarRestart; 113 114 switch (uMsg) 115 { 116 case WM_CREATE: 117 { 118 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); 119 AddTrayIcon (hWnd, true); 120 break; 121 } 122 case WM_CLOSE: 123 { 124 RemoveTrayIcon (hWnd); 125 KillTimer (hWnd, FRAME_UPDATE_TIMER); 126 KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); 127 KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); 128 PostQuitMessage (0); 129 break; 130 } 131 case WM_COMMAND: 132 { 133 switch (LOWORD(wParam)) 134 { 135 case ID_ABOUT: 136 { 137 std::stringstream text; 138 text << "Version: " << I2PD_VERSION << " " << CODENAME; 139 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 140 return 0; 141 } 142 case ID_EXIT: 143 { 144 PostMessage (hWnd, WM_CLOSE, 0, 0); 145 return 0; 146 } 147 case ID_ACCEPT_TRANSIT: 148 { 149 i2p::context.SetAcceptsTunnels (true); 150 std::stringstream text; 151 text << "I2Pd now accept transit tunnels"; 152 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 153 return 0; 154 } 155 case ID_DECLINE_TRANSIT: 156 { 157 i2p::context.SetAcceptsTunnels (false); 158 std::stringstream text; 159 text << "I2Pd now decline new transit tunnels"; 160 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 161 return 0; 162 } 163 case ID_GRACEFUL_SHUTDOWN: 164 { 165 i2p::context.SetAcceptsTunnels (false); 166 SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes 167 SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); // check tunnels every second 168 g_GracefulShutdownEndtime = GetTickCount() + 10*60*1000; 169 i2p::util::DaemonWin32::Instance ().isGraceful = true; 170 return 0; 171 } 172 case ID_STOP_GRACEFUL_SHUTDOWN: 173 { 174 i2p::context.SetAcceptsTunnels (true); 175 KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); 176 KillTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER); 177 g_GracefulShutdownEndtime = 0; 178 i2p::util::DaemonWin32::Instance ().isGraceful = false; 179 return 0; 180 } 181 case ID_RELOAD: 182 { 183 i2p::client::context.ReloadConfig(); 184 std::stringstream text; 185 text << "I2Pd reloading configs..."; 186 MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); 187 return 0; 188 } 189 case ID_CONSOLE: 190 { 191 char buf[30]; 192 std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); 193 uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); 194 snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); 195 ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); 196 return 0; 197 } 198 case ID_APP: 199 { 200 ShowWindow(hWnd, SW_SHOW); 201 SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); 202 return 0; 203 } 204 case ID_DATADIR: 205 { 206 std::string datadir(i2p::fs::GetDataDir()); 207 ShellExecute(NULL, "explore", datadir.c_str(), NULL, NULL, SW_SHOWNORMAL); 208 return 0; 209 } 210 } 211 break; 212 } 213 case WM_SYSCOMMAND: 214 { 215 switch (wParam) 216 { 217 case SC_MINIMIZE: 218 { 219 ShowWindow(hWnd, SW_HIDE); 220 KillTimer (hWnd, FRAME_UPDATE_TIMER); 221 return 0; 222 } 223 case SC_CLOSE: 224 { 225 std::string close; i2p::config::GetOption("close", close); 226 if (0 == close.compare("ask")) 227 switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" 228 " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", 229 "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) 230 { 231 case IDYES: close = "minimize"; break; 232 case IDNO: close = "exit"; break; 233 default: return 0; 234 } 235 if (0 == close.compare("minimize")) 236 { 237 ShowWindow(hWnd, SW_HIDE); 238 KillTimer (hWnd, FRAME_UPDATE_TIMER); 239 return 0; 240 } 241 if (0 != close.compare("exit")) 242 { 243 ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); 244 return 0; 245 } 246 } 247 } 248 [[fallthrough]]; 249 } 250 case WM_TRAYICON: 251 { 252 switch (lParam) 253 { 254 case WM_LBUTTONUP: 255 case WM_RBUTTONUP: 256 { 257 SetForegroundWindow (hWnd); 258 ShowPopupMenu(hWnd, NULL, -1); 259 PostMessage (hWnd, WM_APP + 1, 0, 0); 260 break; 261 } 262 } 263 break; 264 } 265 case WM_TIMER: 266 { 267 switch(wParam) 268 { 269 case IDT_GRACEFUL_SHUTDOWN_TIMER: 270 { 271 g_GracefulShutdownEndtime = 0; 272 PostMessage (hWnd, WM_CLOSE, 0, 0); // exit 273 return 0; 274 } 275 case IDT_GRACEFUL_TUNNELCHECK_TIMER: 276 { 277 if (i2p::tunnel::tunnels.CountTransitTunnels() == 0) 278 PostMessage (hWnd, WM_CLOSE, 0, 0); 279 else 280 SetTimer (hWnd, IDT_GRACEFUL_TUNNELCHECK_TIMER, 1000, nullptr); 281 return 0; 282 } 283 case FRAME_UPDATE_TIMER: 284 { 285 InvalidateRect(hWnd, NULL, TRUE); 286 return 0; 287 } 288 } 289 break; 290 } 291 case WM_PAINT: 292 { 293 HDC hDC; 294 PAINTSTRUCT ps; 295 RECT rp; 296 HFONT hFont; 297 std::stringstream s; i2p::util::PrintMainWindowText (s); 298 hDC = BeginPaint (hWnd, &ps); 299 GetClientRect(hWnd, &rp); 300 SetTextColor(hDC, 0x00D43B69); 301 hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); 302 SelectObject(hDC,hFont); 303 DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); 304 DeleteObject(hFont); 305 EndPaint(hWnd, &ps); 306 break; 307 } 308 default: 309 { 310 if (uMsg == s_uTaskbarRestart) 311 AddTrayIcon (hWnd, false); 312 break; 313 } 314 } 315 return DefWindowProc( hWnd, uMsg, wParam, lParam); 316 } 317 318 bool StartWin32App (bool isWinService) 319 { 320 g_isWinService = isWinService; 321 if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) 322 { 323 MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); 324 return false; 325 } 326 // register main window 327 auto hInst = GetModuleHandle(NULL); 328 WNDCLASSEX wclx; 329 memset (&wclx, 0, sizeof(wclx)); 330 wclx.cbSize = sizeof(wclx); 331 wclx.style = 0; 332 wclx.lpfnWndProc = WndProc; 333 //wclx.cbClsExtra = 0; 334 //wclx.cbWndExtra = 0; 335 wclx.hInstance = hInst; 336 wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); 337 wclx.hCursor = LoadCursor (NULL, IDC_ARROW); 338 //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); 339 wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 340 wclx.lpszMenuName = NULL; 341 wclx.lpszClassName = I2PD_WIN32_CLASSNAME; 342 RegisterClassEx (&wclx); 343 // create new window 344 if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) 345 { 346 MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); 347 return false; 348 } 349 // COM requires message loop to work, which is not implemented in service mode 350 if (!g_isWinService) 351 SubscribeToEvents(); 352 return true; 353 } 354 355 int RunWin32App () 356 { 357 MSG msg; 358 while (GetMessage (&msg, NULL, 0, 0 )) 359 { 360 TranslateMessage (&msg); 361 DispatchMessage (&msg); 362 } 363 return msg.wParam; 364 } 365 366 void StopWin32App () 367 { 368 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 369 if (hWnd) 370 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); 371 else if(!g_isWinService) 372 UnSubscribeFromEvents(); 373 UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); 374 } 375 376 bool GracefulShutdown () 377 { 378 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 379 if (hWnd) 380 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); 381 return hWnd; 382 } 383 384 bool StopGracefulShutdown () 385 { 386 HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); 387 if (hWnd) 388 PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); 389 return hWnd; 390 } 391 392 int GetGracefulShutdownRemainingTime () 393 { 394 if (!g_GracefulShutdownEndtime) return 0; 395 auto remains = (g_GracefulShutdownEndtime - GetTickCount()) / 1000; 396 if (remains < 0) remains = 0; 397 return remains; 398 } 399 } 400 }