/ Win32 / Win32App.cpp
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  }