From 385cfe147d063678486dc7585f5e887586903506 Mon Sep 17 00:00:00 2001 From: neru Date: Sat, 20 Jun 2026 06:53:02 -0300 Subject: [PATCH] feat: add tray icon --- src/unlocker/main.cpp | 56 ++++++++++++- src/unlocker/tray-icon.cpp | 168 +++++++++++++++++++++++++++++++++++++ src/unlocker/tray-icon.h | 27 ++++++ 3 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 src/unlocker/tray-icon.cpp create mode 100644 src/unlocker/tray-icon.h diff --git a/src/unlocker/main.cpp b/src/unlocker/main.cpp index 28ff9c9..2c3676b 100644 --- a/src/unlocker/main.cpp +++ b/src/unlocker/main.cpp @@ -1,4 +1,5 @@ #include "win-platform.h" +#include "tray-icon.h" #include "utils.h" #include "spoofer.h" #include "log-sink.h" @@ -103,11 +104,36 @@ bool run() return false; } - mainLog.info("Proxy running, Ctrl+C to stop."); + mainLog.info("Setting up system tray icon"); + + TrayIcon tray; + tray.setExitCallback([&]() { + running = false; + mainLog.info("Exit requested from system tray."); + }); + + if (!tray.init()) + { + mainLog.error("Failed to initialize system tray icon."); + return false; + } + + HWND consoleWnd = GetConsoleWindow(); + if (consoleWnd) + { + HMENU menuHandle = GetSystemMenu(consoleWnd, FALSE); + if (menuHandle) RemoveMenu(menuHandle, SC_CLOSE, MF_BYCOMMAND); + } + + mainLog.info("Proxy running, Ctrl+C to stop. Check system tray for options."); while (running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + { + tray.processMessages(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } mainLog.info("Shutting down..."); + tray.shutdown(); proxy->shutdown(); cleaner->shutdown(); @@ -116,11 +142,35 @@ bool run() delete conf; conf = nullptr; + ReleaseMutex(mtx); + CloseHandle(mtx); + return true; } -int main() +int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/) { + AllocConsole(); + + /* + ansi seequences + */ + HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); + if (outHandle == INVALID_HANDLE_VALUE) return 1; + + DWORD conMode = 0; + if (!GetConsoleMode(outHandle, &conMode)) return 1; + + conMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(outHandle, conMode); + + /* + io + */ + FILE* dummy; + freopen_s(&dummy, "CONIN$", "r", stdin); + freopen_s(&dummy, "CONOUT$", "w", stdout); + freopen_s(&dummy, "CONOUT$", "w", stderr); /* handlers for cleaning proxy conf on exit / crash / taskkill / whatever diff --git a/src/unlocker/tray-icon.cpp b/src/unlocker/tray-icon.cpp new file mode 100644 index 0000000..677b985 --- /dev/null +++ b/src/unlocker/tray-icon.cpp @@ -0,0 +1,168 @@ +#include "tray-icon.h" +#include "win-platform.h" + +#include +#include + +#define WM_TRAYICON (WM_USER + 1) +#define ID_TRAY_APP_ICON 1001 +#define ID_TRAY_EXIT 1002 +#define ID_TRAY_CONTROL_PANEL 1003 + +TrayIcon* TrayIcon::instance = nullptr; + +TrayIcon::TrayIcon() : _hwnd(nullptr), _isConsoleVisible(true) +{ + instance = this; +} + +TrayIcon::~TrayIcon() +{ + shutdown(); + instance = nullptr; +} + +void* TrayIcon::windowProc(void* h, unsigned int msg, unsigned long long wParam, long long lParam) +{ + HWND hwnd = reinterpret_cast(h); + + if (msg == WM_DESTROY) + { + PostQuitMessage(0); + return 0; + } + + if (msg == WM_TRAYICON) + { + if (lParam == WM_LBUTTONUP && instance) + instance->onToggleConsole(); + else if (lParam == WM_RBUTTONUP) + { + POINT curPoint; + GetCursorPos(&curPoint); + + HMENU popupHandle = CreatePopupMenu(); + InsertMenuA(popupHandle, 0, MF_BYPOSITION | MF_STRING, ID_TRAY_CONTROL_PANEL, "Open Control Panel"); + InsertMenuA(popupHandle, 1, MF_BYPOSITION | MF_STRING, ID_TRAY_EXIT, "Exit"); + + SetForegroundWindow(hwnd); + + UINT clicked = + TrackPopupMenu(popupHandle, TPM_RETURNCMD | TPM_NONOTIFY, curPoint.x, curPoint.y, 0, hwnd, NULL); + + if (clicked == ID_TRAY_CONTROL_PANEL && instance) + instance->onOpenControlPanel(); + else if (clicked == ID_TRAY_EXIT && instance) + instance->onExit(); + + DestroyMenu(popupHandle); + } + } + return reinterpret_cast(DefWindowProcA(hwnd, msg, wParam, lParam)); +} + +bool TrayIcon::init() +{ + WNDCLASSEXA wc = {0}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = (WNDPROC)windowProc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = "HexUnlockedTrayIconClass"; + + if (!RegisterClassExA(&wc)) return false; + + HWND window = CreateWindowExA(0, "HexUnlockedTrayIconClass", "HexUnlocked", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, + wc.hInstance, NULL); + if (!window) return false; + + this->_hwnd = window; + + NOTIFYICONDATAA nid = {0}; + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = window; + nid.uID = ID_TRAY_APP_ICON; + nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + nid.uCallbackMessage = WM_TRAYICON; + + nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); + strncpy_s(nid.szTip, "HexUnlocked", sizeof(nid.szTip) - 1); + nid.szTip[sizeof(nid.szTip) - 1] = '\0'; + + Shell_NotifyIconA(NIM_ADD, &nid); + + return true; +} + +void TrayIcon::shutdown() +{ + if (_hwnd) + { + NOTIFYICONDATAA nid = {0}; + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = reinterpret_cast(_hwnd); + nid.uID = ID_TRAY_APP_ICON; + Shell_NotifyIconA(NIM_DELETE, &nid); + + DestroyWindow(reinterpret_cast(_hwnd)); + UnregisterClassA("HexUnlockedTrayIconClass", GetModuleHandle(NULL)); + _hwnd = nullptr; + } +} + +void TrayIcon::processMessages() { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + break; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + HWND consoleWnd = GetConsoleWindow(); + if (consoleWnd && IsIconic(consoleWnd)) { + ShowWindow(consoleWnd, SW_HIDE); + _isConsoleVisible = false; + } +} + +void TrayIcon::onToggleConsole() { + HWND consoleWnd = GetConsoleWindow(); + if (!consoleWnd) return; + + if (_isConsoleVisible) { + ShowWindow(consoleWnd, SW_HIDE); + _isConsoleVisible = false; + } else { + ShowWindow(consoleWnd, SW_RESTORE); + + HWND hCurWnd = GetForegroundWindow(); + DWORD dwMyID = GetCurrentThreadId(); + DWORD dwCurID = GetWindowThreadProcessId(hCurWnd, NULL); + AttachThreadInput(dwCurID, dwMyID, TRUE); + SetWindowPos(consoleWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + SetWindowPos(consoleWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(consoleWnd); + SetFocus(consoleWnd); + SetActiveWindow(consoleWnd); + AttachThreadInput(dwCurID, dwMyID, FALSE); + + _isConsoleVisible = true; + } +} + +void TrayIcon::onOpenControlPanel() +{ + ShellExecuteA(NULL, "open", "https://dbd.neru.rip", NULL, NULL, SW_SHOWNORMAL); +} + +void TrayIcon::onExit() +{ + if (_exitCallback) + _exitCallback(); +} + +void TrayIcon::setExitCallback(std::function callback) +{ + _exitCallback = callback; +} diff --git a/src/unlocker/tray-icon.h b/src/unlocker/tray-icon.h new file mode 100644 index 0000000..bdba707 --- /dev/null +++ b/src/unlocker/tray-icon.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class TrayIcon { +public: + TrayIcon(); + ~TrayIcon(); + + bool init(); + void shutdown(); + void processMessages(); + + void onToggleConsole(); + void onOpenControlPanel(); + void onExit(); + + void setExitCallback(std::function callback); + +private: + static void* windowProc(void* hwnd, unsigned int msg, unsigned long long wParam, long long lParam); + static TrayIcon* instance; + + void* _hwnd; + bool _isConsoleVisible; + std::function _exitCallback; +};