├── .gitignore ├── release_version.txt ├── README.md ├── HexViewer ├── include │ ├── system │ │ ├── targetver.h │ │ ├── resource.h │ │ └── update.h │ ├── ui │ │ ├── framework.h │ │ ├── contextmenu.h │ │ ├── options.h │ │ ├── notification.h │ │ ├── searchdialog.h │ │ ├── about.h │ │ ├── darkmode.h │ │ └── menu.h │ └── core │ │ ├── HexData.h │ │ ├── render.h │ │ └── imageloader.h ├── resources │ ├── icons │ │ ├── small.ico │ │ └── HexViewer.ico │ ├── ui │ │ ├── HexViewer.rc │ │ ├── HexViewer.aps │ │ ├── RCb16320 │ │ ├── RCc16320 │ │ ├── RCg16320 │ │ ├── RCa10148 │ │ ├── RCa16320 │ │ ├── RCb10148 │ │ ├── RCd16320 │ │ ├── RCe16320 │ │ └── RCf16320 │ └── images │ │ └── about.png └── src │ ├── ui │ ├── contextmenu.cpp │ ├── notification.cpp │ ├── menu.cpp │ ├── options.cpp │ └── about.cpp │ └── core │ ├── HexData.cpp │ └── render.cpp ├── HexViewerExten ├── res │ ├── main.bmp │ ├── resource.h │ └── Resource.rc ├── include │ ├── framework.h │ ├── ClassFactory.h │ ├── pch.h │ ├── ShellEx.h │ └── Reg.h ├── src │ ├── pch.cpp │ ├── ClassFactory.cpp │ ├── dllmain.cpp │ ├── Reg.cpp │ └── ShellEx.cpp └── CMakeLists.txt ├── .gitmodules ├── docs └── BUILD.md ├── .github └── workflows │ └── build.yml ├── LICENSE └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /release_version.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HexViewer 2 | Hex Viewer 3 | -------------------------------------------------------------------------------- /HexViewer/include/system/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /HexViewerExten/res/main.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewerExten/res/main.bmp -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "XCapstone"] 2 | path = XCapstone 3 | url = https://github.com/horsicq/XCapstone.git 4 | -------------------------------------------------------------------------------- /HexViewer/resources/icons/small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewer/resources/icons/small.ico -------------------------------------------------------------------------------- /HexViewer/resources/ui/HexViewer.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewer/resources/ui/HexViewer.rc -------------------------------------------------------------------------------- /HexViewer/resources/images/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewer/resources/images/about.png -------------------------------------------------------------------------------- /HexViewer/resources/ui/HexViewer.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewer/resources/ui/HexViewer.aps -------------------------------------------------------------------------------- /HexViewer/resources/icons/HexViewer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsicq/HexViewer/main/HexViewer/resources/icons/HexViewer.ico -------------------------------------------------------------------------------- /HexViewerExten/include/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | #include 5 | -------------------------------------------------------------------------------- /HexViewer/include/ui/framework.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "targetver.h" 5 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCb16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include <resource.h> 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCc16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include <resource.h> 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCg16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include "resource.h" 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewerExten/res/resource.h: -------------------------------------------------------------------------------- 1 | 2 | #define IDB_OK 101 3 | 4 | #ifdef APSTUDIO_INVOKED 5 | #ifndef APSTUDIO_READONLY_SYMBOLS 6 | #define _APS_NEXT_RESOURCE_VALUE 102 7 | #define _APS_NEXT_COMMAND_VALUE 40001 8 | #define _APS_NEXT_CONTROL_VALUE 1001 9 | #define _APS_NEXT_SYMED_VALUE 101 10 | #endif 11 | #endif -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCa10148: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include <..\include\resource.h> 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCa16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include <..\include\resource.h> 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCb10148: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include <..\include\resource.h> 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCd16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include "include\system\resource.h" 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCe16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include "..\include\system\resource.h" 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /HexViewer/resources/ui/RCf16320: -------------------------------------------------------------------------------- 1 | #line 1"D:\\Users\\DevX-Cipher\\source\\repos\\HexViewer\\HexViewer\\resources\\ui\\HexViewer.rc" 2 | #line 2 3 | #include "..\include\system\resource.h" 4 | IDB_LOGO RCDATA "about.png" 5 | -------------------------------------------------------------------------------- /docs/BUILD.md: -------------------------------------------------------------------------------- 1 | # How to build on Linux based on Debian 2 | 3 | #### Install packages: 4 | 5 | ```bash 6 | sudo apt-get install git build-essential libcapstone-dev -y 7 | ``` 8 | 9 | #### Clone this repo recursively: 10 | 11 | ```bash 12 | git clone --recursive https://github.com/horsicq/HexViewer.git 13 | cd HexViewer 14 | ``` 15 | 16 | #### Run build script: 17 | 18 | ```bash 19 | chmod a+x build_linux.sh 20 | bash -x build_linux.sh 21 | ``` -------------------------------------------------------------------------------- /HexViewer/include/system/resource.h: -------------------------------------------------------------------------------- 1 | #ifndef RESOURCE_H 2 | #define RESOURCE_H 3 | 4 | #define IDB_LOGO 1 5 | 6 | #ifdef APSTUDIO_INVOKED 7 | #ifndef APSTUDIO_READONLY_SYMBOLS 8 | 9 | #define _APS_NO_MFC 130 10 | #define _APS_NEXT_RESOURCE_VALUE 2 // Next available ID after your logo 11 | #define _APS_NEXT_COMMAND_VALUE 32771 12 | #define _APS_NEXT_CONTROL_VALUE 1000 13 | #define _APS_NEXT_SYMED_VALUE 110 14 | 15 | #endif 16 | #endif 17 | 18 | #endif // RESOURCE_H 19 | -------------------------------------------------------------------------------- /HexViewerExten/include/ClassFactory.h: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | 4 | 5 | #include 6 | 7 | 8 | class ClassFactory : public IClassFactory 9 | { 10 | public: 11 | IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); 12 | IFACEMETHODIMP_(ULONG) AddRef(); 13 | IFACEMETHODIMP_(ULONG) Release(); 14 | 15 | IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void **ppv); 16 | IFACEMETHODIMP LockServer(BOOL fLock); 17 | 18 | ClassFactory(); 19 | 20 | protected: 21 | ~ClassFactory(); 22 | 23 | private: 24 | long m_cRef; 25 | }; 26 | -------------------------------------------------------------------------------- /HexViewerExten/include/pch.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PCH_H 3 | #define PCH_H 4 | 5 | #include "framework.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "Reg.h" 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | 21 | void LogMessage(const std::wstring& message); 22 | 23 | 24 | 25 | 26 | #define INITGUID 27 | #include 28 | const CLSID CLSID_HexViewer = 29 | { 0x1AC8EAF1, 0x9A6A, 0x471C, 0xB8, 0x7B, 0xB1, 0xDF, 0x2B, 0x8E, 0xA6, 0x2F } ; 30 | 31 | #endif //PCH_H 32 | -------------------------------------------------------------------------------- /HexViewerExten/src/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | void LogMessage(const std::wstring& message) { 4 | #ifdef _DEBUG 5 | wchar_t desktopPath[MAX_PATH]; 6 | if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, desktopPath))) { 7 | std::wstring logFilePath = std::wstring(desktopPath) + L"\\ServiceLog.txt"; 8 | std::wofstream logFile(logFilePath, std::ios::app); 9 | if (logFile.is_open()) { 10 | logFile << message << std::endl; 11 | logFile.close(); 12 | } 13 | else { 14 | fprint(stderr, "Failed to open log file."); 15 | } 16 | } 17 | else { 18 | fprintf(stderr, "Failed to get desktop directory."); 19 | } 20 | #endif 21 | } 22 | -------------------------------------------------------------------------------- /HexViewer/include/ui/contextmenu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | 7 | enum class UserRole { 8 | CurrentUser, // HKEY_CURRENT_USER (no admin needed) 9 | AllUsers // HKEY_CLASSES_ROOT (requires admin) 10 | }; 11 | 12 | class ContextMenuRegistry { 13 | public: 14 | static bool Register(const wchar_t* exePath, UserRole role = UserRole::CurrentUser); 15 | 16 | static bool Unregister(UserRole role = UserRole::CurrentUser); 17 | 18 | static bool IsRegistered(UserRole role = UserRole::CurrentUser); 19 | 20 | private: 21 | static HKEY GetRootKey(UserRole role); 22 | static std::wstring GetRegistryPath(UserRole role); 23 | static bool SetRegistryValue(HKEY hKey, const wchar_t* valueName, const wchar_t* data); 24 | static bool DeleteRegistryKey(HKEY hRootKey, const wchar_t* subKey); 25 | }; 26 | 27 | #endif // _WIN32 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu's CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build-linux: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Install required libs 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install git build-essential cmake libcapstone-dev -y 24 | 25 | - name: Build HexViewer 26 | run: | 27 | chmod a+x build_linux.sh 28 | ./build_linux.sh 29 | 30 | - name: Upload Release as Download 31 | if: github.event_name != 'pull_request' 32 | uses: softprops/action-gh-release@v2 33 | env: 34 | RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} 35 | with: 36 | tag_name: Beta 37 | draft: false 38 | prerelease: true 39 | files: | 40 | /home/runner/work/HexViewer/HexViewer/build/bin/HexViewer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /HexViewer/include/ui/options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #ifdef _WIN32 4 | #include 5 | typedef HWND NativeWindow; 6 | #elif __APPLE__ 7 | typedef void* NativeWindow; 8 | #else 9 | typedef unsigned long NativeWindow; 10 | #endif 11 | struct AppOptions { 12 | bool darkMode; 13 | int defaultBytesPerLine; 14 | bool autoReload; 15 | bool contextMenu; 16 | std::string language; 17 | 18 | AppOptions() 19 | : darkMode(false), 20 | defaultBytesPerLine(16), 21 | autoReload(true), 22 | contextMenu(false), 23 | language("English") { 24 | } 25 | 26 | AppOptions(bool dark, int bpl, bool reload, bool ctx, const std::string& lang) 27 | : darkMode(dark), 28 | defaultBytesPerLine(bpl), 29 | autoReload(reload), 30 | contextMenu(ctx), 31 | language(lang) { 32 | } 33 | }; 34 | inline bool g_isNative = false; 35 | inline bool g_isMsix = false; 36 | void DetectNative(); 37 | void LoadOptionsFromFile(AppOptions& options); 38 | void SaveOptionsToFile(const AppOptions& options); 39 | class OptionsDialog { 40 | public: 41 | #ifdef _WIN32 42 | static bool Show(HWND parent, AppOptions& options); 43 | #else 44 | static bool Show(NativeWindow parent, AppOptions& options); 45 | #endif 46 | }; 47 | -------------------------------------------------------------------------------- /HexViewerExten/include/ShellEx.h: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | 5 | class HexViewer : 6 | public IShellExtInit, 7 | public IContextMenu 8 | { 9 | public: 10 | 11 | 12 | 13 | IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); 14 | IFACEMETHODIMP_(ULONG) AddRef(); 15 | IFACEMETHODIMP_(ULONG) Release(); 16 | 17 | IFACEMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID); 18 | 19 | 20 | IFACEMETHODIMP QueryContextMenu(HMENU hMenu, UINT index, UINT uIDFirst, UINT uIDLast, UINT uFlags); 21 | IFACEMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici); 22 | IFACEMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax); 23 | 24 | HexViewer(void); 25 | 26 | 27 | 28 | protected: 29 | ~HexViewer(void); 30 | 31 | private: 32 | 33 | wchar_t m_szSelectedFile[MAX_PATH]; 34 | PCWSTR m_pszMenuText; 35 | 36 | HANDLE m_hMenuBmp; 37 | PCSTR m_pszVerb; 38 | PCWSTR m_pwszVerb; 39 | PCSTR m_pszVerbCanonicalName; 40 | PCWSTR m_pwszVerbCanonicalName; 41 | PCSTR m_pszVerbHelpText; 42 | PCWSTR m_pwszVerbHelpText; 43 | LPWSTR m_pwszLogFileName; 44 | 45 | UINT m_nFiles; 46 | 47 | 48 | long m_cRef; 49 | 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /HexViewerExten/include/Reg.h: -------------------------------------------------------------------------------- 1 | /****************************** Module Header ******************************\ 2 | Module Name: Reg.h 3 | Project: CppShellExtContextMenuHandler 4 | Copyright (c) Microsoft Corporation. 5 | 6 | The file declares reusable helper functions to register and unregister 7 | in-process COM components and shell context menu handlers in the registry. 8 | 9 | RegisterInprocServer - register the in-process component in the registry. 10 | UnregisterInprocServer - unregister the in-process component in the registry. 11 | RegisterShellExtContextMenuHandler - register the context menu handler. 12 | UnregisterShellExtContextMenuHandler - unregister the context menu handler. 13 | 14 | This source is subject to the Microsoft Public License. 15 | See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. 16 | All other rights reserved. 17 | 18 | THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 19 | EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. 21 | \***************************************************************************/ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | HRESULT RegisterInprocServer(PCWSTR pszModule, const CLSID& clsid, 28 | PCWSTR pszFriendlyName, PCWSTR pszThreadModel); 29 | HRESULT UnregisterInprocServer(const CLSID& clsid); 30 | HRESULT RegisterShellExtContextMenuHandler( 31 | PCWSTR pszFileType, const CLSID& clsid, PCWSTR pszFriendlyName); 32 | HRESULT UnregisterShellExtContextMenuHandler( 33 | PCWSTR pszFileType, const CLSID& clsid); 34 | -------------------------------------------------------------------------------- /HexViewer/include/ui/notification.h: -------------------------------------------------------------------------------- 1 | #ifndef APP_NOTIFICATION_H 2 | #define APP_NOTIFICATION_H 3 | 4 | #include 5 | 6 | class AppNotification { 7 | public: 8 | enum class NotificationIcon { 9 | Deafult, 10 | Info, 11 | Warning, 12 | Error 13 | }; 14 | 15 | static AppNotification& GetInstance(); 16 | 17 | static bool IsAvailable(); 18 | 19 | static void Show(const std::string& title, 20 | const std::string& message, 21 | const std::string& appId = "HexViewer", 22 | NotificationIcon icon = NotificationIcon::Info, 23 | int timeoutMs = 5000); 24 | 25 | private: 26 | AppNotification(); 27 | ~AppNotification(); 28 | AppNotification(const AppNotification&) = delete; 29 | AppNotification& operator=(const AppNotification&) = delete; 30 | 31 | #ifdef _WIN32 32 | bool m_initialized; 33 | bool m_shortcutCreated; 34 | bool EnsureShortcutExists(const std::wstring& appId); 35 | void ShowWindows(const std::string& title, 36 | const std::string& message, 37 | const std::string& appId, 38 | NotificationIcon icon, 39 | int timeoutMs); 40 | void ShowWindowsFallback(const std::string& title, 41 | const std::string& message, 42 | NotificationIcon icon); 43 | static std::wstring EscapeXml(const std::wstring& str); 44 | #endif 45 | 46 | #ifdef __linux__ 47 | void ShowLinux(const std::string& title, 48 | const std::string& message, 49 | NotificationIcon icon, 50 | int timeoutMs); 51 | #endif 52 | 53 | #ifdef __APPLE__ 54 | void ShowMacOS(const std::string& title, 55 | const std::string& message); 56 | #endif 57 | }; 58 | 59 | #endif // APP_NOTIFICATION_H 60 | -------------------------------------------------------------------------------- /HexViewerExten/src/ClassFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "ClassFactory.h" 4 | #include 5 | 6 | #include "ShellEx.h" 7 | 8 | extern long g_cDllRef; 9 | 10 | ClassFactory::ClassFactory() : m_cRef(1) 11 | { 12 | InterlockedIncrement(&g_cDllRef); 13 | } 14 | 15 | ClassFactory::~ClassFactory() 16 | { 17 | InterlockedDecrement(&g_cDllRef); 18 | } 19 | 20 | IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) 21 | { 22 | static const QITAB qit[] = 23 | { 24 | QITABENT(ClassFactory, IClassFactory), 25 | { 0 }, 26 | }; 27 | return QISearch(this, qit, riid, ppv); 28 | } 29 | 30 | IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() 31 | { 32 | return InterlockedIncrement(&m_cRef); 33 | } 34 | 35 | IFACEMETHODIMP_(ULONG) ClassFactory::Release() 36 | { 37 | ULONG cRef = InterlockedDecrement(&m_cRef); 38 | if (0 == cRef) 39 | { 40 | delete this; 41 | } 42 | return cRef; 43 | } 44 | 45 | IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) 46 | { 47 | HRESULT hr = CLASS_E_NOAGGREGATION; 48 | 49 | if (pUnkOuter == NULL) 50 | { 51 | hr = E_OUTOFMEMORY; 52 | 53 | 54 | HexViewer* pExt2 = new (std::nothrow) HexViewer(); 55 | if (pExt2) 56 | { 57 | hr = pExt2->QueryInterface(riid, ppv); 58 | pExt2->Release(); 59 | } 60 | } 61 | 62 | return hr; 63 | } 64 | 65 | IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) 66 | { 67 | if (fLock) 68 | { 69 | InterlockedIncrement(&g_cDllRef); 70 | } 71 | else 72 | { 73 | InterlockedDecrement(&g_cDllRef); 74 | } 75 | return S_OK; 76 | } -------------------------------------------------------------------------------- /HexViewer/include/ui/searchdialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "render.h" 3 | #include 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #elif defined(__APPLE__) 10 | #include 11 | typedef void* id; 12 | #elif defined(__linux__) 13 | #include 14 | #endif 15 | 16 | struct PlatformWindow { 17 | #ifdef _WIN32 18 | HWND hwnd; 19 | #elif defined(__APPLE__) 20 | id window; 21 | id view; 22 | #elif defined(__linux__) 23 | Display* display; 24 | Window window; 25 | Atom wmDeleteWindow; 26 | #endif 27 | }; 28 | 29 | struct FindReplaceDialogData { 30 | std::string findText; 31 | std::string replaceText; 32 | int activeTextBox = 0; 33 | int hoveredWidget = -1; 34 | int pressedWidget = -1; 35 | bool dialogResult = false; 36 | bool running = true; 37 | RenderManager* renderer = nullptr; 38 | PlatformWindow platformWindow = {}; 39 | std::function callback; 40 | }; 41 | 42 | struct GoToDialogData { 43 | std::string lineNumberText; 44 | int activeTextBox = 0; 45 | int hoveredWidget = -1; 46 | int pressedWidget = -1; 47 | bool dialogResult = false; 48 | bool running = true; 49 | RenderManager* renderer = nullptr; 50 | PlatformWindow platformWindow = {}; 51 | std::function callback; 52 | }; 53 | 54 | namespace SearchDialogs { 55 | 56 | void CleanupDialogs(); 57 | 58 | void ShowFindReplaceDialog(void* parentHandle, bool darkMode, 59 | std::function callback); 60 | 61 | void ShowGoToDialog(void* parentHandle, bool darkMode, 62 | std::function callback); 63 | 64 | } // namespace SearchDialogs 65 | -------------------------------------------------------------------------------- /HexViewer/include/ui/about.h: -------------------------------------------------------------------------------- 1 | #ifndef ABOUT_H 2 | #define ABOUT_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | typedef HWND NativeWindow; 10 | #elif defined(__APPLE__) 11 | typedef void* NativeWindow; 12 | #elif defined(__linux__) 13 | #include 14 | typedef Window NativeWindow; 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | class AboutDialog { 23 | public: 24 | static void Show(NativeWindow parent, bool isDarkMode); 25 | private: 26 | static RenderManager* renderer; 27 | static bool darkMode; 28 | static NativeWindow parentWindow; 29 | static int hoveredButton; 30 | static int pressedButton; 31 | static Rect updateButtonRect; 32 | static Rect closeButtonRect; 33 | static bool betaEnabled; 34 | static Rect betaToggleRect; 35 | static bool betaToggleHovered; 36 | static void RenderContent(int width, int height); 37 | 38 | #ifdef _WIN32 39 | static LRESULT CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 40 | static void OnPaint(HWND hWnd); 41 | static void OnMouseMove(HWND hWnd, int x, int y); 42 | static void OnMouseDown(HWND hWnd, int x, int y); 43 | static bool OnMouseUp(HWND hWnd, int x, int y); 44 | #elif defined(__linux__) 45 | static void OnPaint(); 46 | static void OnMouseMove(int x, int y); 47 | static void OnMouseDown(int x, int y); 48 | static bool OnMouseUp(int x, int y); 49 | #elif defined(__APPLE__) 50 | static void OnPaint(); 51 | static void OnMouseMove(int x, int y); 52 | static void OnMouseDown(int x, int y); 53 | static bool OnMouseUp(int x, int y); 54 | #endif 55 | }; 56 | 57 | #endif // ABOUT_H 58 | -------------------------------------------------------------------------------- /HexViewer/include/core/HexData.h: -------------------------------------------------------------------------------- 1 | #ifndef HEXDATA_H 2 | #define HEXDATA_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class HexData { 9 | public: 10 | HexData(); 11 | ~HexData(); 12 | bool loadFile(const char* filepath); 13 | bool saveFile(const char* filepath); 14 | void clear(); 15 | const std::vector& getHexLines() const { return hexLines; } 16 | const std::vector& getDisassemblyLines() const { return disassemblyLines; } 17 | std::string getHeaderLine() const { return headerLine; } 18 | size_t getFileSize() const { return fileData.size(); } 19 | bool isEmpty() const { return fileData.empty(); } 20 | int getCurrentBytesPerLine() const { return currentBytesPerLine; } 21 | bool editByte(size_t offset, uint8_t newValue); 22 | uint8_t getByte(size_t offset) const; 23 | uint8_t readByte(size_t offset) const { return getByte(offset); } 24 | bool isModified() const { return modified; } 25 | void setModified(bool mod) { modified = mod; } 26 | void regenerateHexLines(int bytesPerLine); 27 | void setArchitecture(cs_arch arch, cs_mode mode); 28 | 29 | private: 30 | void convertDataToHex(int bytesPerLine); 31 | void generateHeader(int bytesPerLine); 32 | void generateDisassembly(int bytesPerLine); 33 | std::string disassembleInstruction(size_t offset, int& instructionLength); 34 | bool initializeCapstone(); 35 | void cleanupCapstone(); 36 | 37 | std::vector fileData; 38 | std::vector hexLines; 39 | std::vector disassemblyLines; 40 | std::string headerLine; 41 | std::string filename; 42 | int currentBytesPerLine; 43 | bool modified; 44 | csh csHandle; 45 | bool capstoneInitialized; 46 | cs_arch currentArch; 47 | cs_mode currentMode; 48 | }; 49 | #endif // HEXDATA_H 50 | -------------------------------------------------------------------------------- /HexViewerExten/res/Resource.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | 11 | #include 12 | #include 13 | 14 | ///////////////////////////////////////////////////////////////////////////// 15 | #undef APSTUDIO_READONLY_SYMBOLS 16 | 17 | ///////////////////////////////////////////////////////////////////////////// 18 | // Chinese (Simplified, PRC) resources 19 | 20 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) 21 | LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED 22 | 23 | #ifdef APSTUDIO_INVOKED 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // TEXTINCLUDE 27 | // 28 | 29 | 1 TEXTINCLUDE 30 | BEGIN 31 | "resource.h\0" 32 | END 33 | 34 | 2 TEXTINCLUDE 35 | BEGIN 36 | "#include ""afxres.h""\r\n" 37 | "\0" 38 | END 39 | 40 | 3 TEXTINCLUDE 41 | BEGIN 42 | "\r\n" 43 | "\0" 44 | END 45 | 46 | #endif // APSTUDIO_INVOKED 47 | 48 | #endif // Chinese (Simplified, PRC) resources 49 | ///////////////////////////////////////////////////////////////////////////// 50 | 51 | 52 | ///////////////////////////////////////////////////////////////////////////// 53 | // English (United States) resources 54 | 55 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 56 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Bitmap 61 | // 62 | 63 | IDB_OK BITMAP "..\\HexViewerExten\\res\\main.bmp" 64 | #endif // English (United States) resources 65 | ///////////////////////////////////////////////////////////////////////////// 66 | 67 | 68 | 69 | #ifndef APSTUDIO_INVOKED 70 | ///////////////////////////////////////////////////////////////////////////// 71 | // 72 | // Generated from the TEXTINCLUDE 3 resource. 73 | // 74 | 75 | 76 | ///////////////////////////////////////////////////////////////////////////// 77 | #endif // not APSTUDIO_INVOKED 78 | 79 | -------------------------------------------------------------------------------- /HexViewer/include/ui/darkmode.h: -------------------------------------------------------------------------------- 1 | #ifndef DARKMODE_H 2 | #define DARKMODE_H 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #endif 10 | 11 | #ifdef __APPLE__ 12 | #include 13 | #endif 14 | 15 | #ifdef __linux__ 16 | #include 17 | #include 18 | #include 19 | #endif 20 | 21 | #ifdef _WIN32 22 | 23 | inline void ApplyDarkTitleBar(HWND hWnd, bool dark) 24 | { 25 | BOOL value = dark ? TRUE : FALSE; 26 | DwmSetWindowAttribute(hWnd, 19, &value, sizeof(value)); 27 | DwmSetWindowAttribute(hWnd, 20, &value, sizeof(value)); 28 | } 29 | 30 | inline void ApplyDarkMenu(HMENU hMenu, bool dark) 31 | { 32 | MENUINFO mi = { sizeof(mi) }; 33 | mi.fMask = MIM_BACKGROUND | MIM_APPLYTOSUBMENUS; 34 | mi.hbrBack = dark ? CreateSolidBrush(RGB(32, 32, 32)) 35 | : (HBRUSH)(COLOR_MENU + 1); 36 | SetMenuInfo(hMenu, &mi); 37 | } 38 | 39 | #elif defined(__APPLE__) 40 | 41 | inline void ApplyDarkTitleBar(NSWindow* window, bool dark) 42 | { 43 | if (!window) return; 44 | NSAppearanceName name = dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua; 45 | [window setAppearance : [NSAppearance appearanceNamed : name] ] ; 46 | [window setTitleVisibility : NSWindowTitleVisible] ; 47 | [window setTitlebarAppearsTransparent : NO] ; 48 | } 49 | 50 | inline void ApplyDarkMenu(NSMenu* menu, bool dark) 51 | { 52 | if (!menu) return; 53 | NSAppearanceName name = dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua; 54 | NSAppearance* appearance = [NSAppearance appearanceNamed : name]; 55 | [NSApp setAppearance : appearance] ; 56 | if ([menu respondsToSelector : @selector(setAppearance:)]) { 57 | [menu setAppearance : appearance] ; 58 | } 59 | } 60 | 61 | #elif defined(__linux__) 62 | 63 | inline void ApplyDarkTitleBar(Display* dpy, Window win, bool dark) 64 | { 65 | Atom atom = XInternAtom(dpy, "_GTK_THEME_VARIANT", False); 66 | if (atom == None) return; 67 | 68 | if (dark) { 69 | const char* value = "dark"; 70 | XChangeProperty(dpy, win, atom, XA_STRING, 8, 71 | PropModeReplace, 72 | (unsigned char*)value, 73 | (int)strlen(value) + 1); 74 | } 75 | else { 76 | XDeleteProperty(dpy, win, atom); 77 | } 78 | } 79 | 80 | inline void ApplyDarkMenu(void*, bool) {} 81 | 82 | #endif 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /HexViewer/include/system/update.h: -------------------------------------------------------------------------------- 1 | #ifndef UPDATE_H 2 | #define UPDATE_H 3 | 4 | #include 5 | #include "render.h" 6 | 7 | #ifdef _WIN32 8 | #include 9 | typedef HWND NativeWindow; 10 | #elif defined(__APPLE__) 11 | typedef void* NativeWindow; 12 | #elif defined(__linux__) 13 | #include 14 | typedef Window NativeWindow; 15 | #endif 16 | 17 | extern bool g_isNative; 18 | 19 | struct UpdateInfo { 20 | bool updateAvailable; 21 | std::string currentVersion; 22 | std::string latestVersion; 23 | std::string releaseNotes; 24 | std::string downloadUrl; 25 | std::string releaseApiUrl; 26 | bool betaPreference = false; 27 | }; 28 | 29 | enum class DownloadState { 30 | Idle, 31 | Connecting, 32 | Downloading, 33 | Installing, 34 | Complete, 35 | Error 36 | }; 37 | 38 | std::string ExtractJsonValue(const std::string& json, const std::string& key); 39 | std::string HttpGet(const std::string& url); 40 | 41 | class UpdateDialog { 42 | public: 43 | static bool Show(NativeWindow parent, const UpdateInfo& info); 44 | static UpdateInfo currentInfo; 45 | static NativeWindow parentWindow; 46 | 47 | private: 48 | static RenderManager* renderer; 49 | static bool darkMode; 50 | static int hoveredButton; 51 | static int pressedButton; 52 | static int scrollOffset; 53 | static bool scrollbarHovered; 54 | static bool scrollbarPressed; 55 | static int scrollDragStart; 56 | static int scrollOffsetStart; 57 | static Rect updateButtonRect; 58 | static Rect skipButtonRect; 59 | static Rect closeButtonRect; 60 | static Rect scrollbarRect; 61 | static Rect scrollThumbRect; 62 | 63 | static void RenderContent(int width, int height); 64 | 65 | #ifdef _WIN32 66 | static LRESULT CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 67 | static void OnPaint(HWND hWnd); 68 | static void OnMouseMove(HWND hWnd, int x, int y); 69 | static void OnMouseDown(HWND hWnd, int x, int y); 70 | static bool OnMouseUp(HWND hWnd, int x, int y); 71 | #elif defined(__linux__) 72 | static void OnPaint(); 73 | static void OnMouseMove(int x, int y); 74 | static void OnMouseDown(int x, int y); 75 | static bool OnMouseUp(int x, int y); 76 | #elif defined(__APPLE__) 77 | static void OnPaint(); 78 | static void OnMouseMove(int x, int y); 79 | static void OnMouseDown(int x, int y); 80 | static bool OnMouseUp(int x, int y); 81 | #endif 82 | }; 83 | 84 | #endif // UPDATE_H 85 | -------------------------------------------------------------------------------- /HexViewerExten/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(HexViewerExten LANGUAGES CXX RC) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | set(HEADERS 9 | include/ClassFactory.h 10 | include/framework.h 11 | include/pch.h 12 | include/Reg.h 13 | include/ShellEx.h 14 | res/resource.h 15 | ) 16 | 17 | set(SOURCES 18 | src/ClassFactory.cpp 19 | src/dllmain.cpp 20 | src/pch.cpp 21 | src/Reg.cpp 22 | src/ShellEx.cpp 23 | ) 24 | 25 | set(RESOURCES 26 | res/Resource.rc 27 | ) 28 | 29 | add_library(HexViewerExten SHARED ${SOURCES} ${HEADERS} ${RESOURCES}) 30 | 31 | target_precompile_headers(HexViewerExten PRIVATE include/pch.h) 32 | 33 | target_include_directories(HexViewerExten PRIVATE 34 | ${CMAKE_CURRENT_SOURCE_DIR}/include 35 | ${CMAKE_CURRENT_SOURCE_DIR}/res 36 | ) 37 | 38 | target_compile_definitions(HexViewerExten PRIVATE 39 | DIESHELLEXTENSION_EXPORTS 40 | _WINDOWS 41 | _USRDLL 42 | UNICODE 43 | _UNICODE 44 | ) 45 | 46 | if(WIN32) 47 | target_compile_definitions(HexViewerExten PRIVATE WIN32) 48 | endif() 49 | 50 | if(MSVC) 51 | target_compile_options(HexViewerExten PRIVATE /W3 /sdl /permissive-) 52 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 53 | target_compile_options(HexViewerExten PRIVATE 54 | /O1 55 | /GL 56 | /Gy 57 | ) 58 | target_link_options(HexViewerExten PRIVATE 59 | /LTCG 60 | /OPT:REF 61 | /OPT:ICF 62 | ) 63 | set_property(TARGET HexViewerExten PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") 64 | else() 65 | set_property(TARGET HexViewerExten PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") 66 | endif() 67 | else() 68 | target_compile_options(HexViewerExten PRIVATE -Wall -Wextra -Wpedantic) 69 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 70 | target_compile_options(HexViewerExten PRIVATE -Os -s) 71 | endif() 72 | endif() 73 | 74 | if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Release") 75 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") 76 | string(REPLACE "/DEBUG" "" CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 77 | endif() 78 | 79 | if(MSVC) 80 | set_target_properties(HexViewerExten PROPERTIES 81 | LINK_FLAGS "/SUBSYSTEM:WINDOWS" 82 | ) 83 | endif() 84 | 85 | set_target_properties(HexViewerExten PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 87 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 88 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 89 | ) 90 | 91 | install(TARGETS HexViewerExten 92 | RUNTIME DESTINATION bin 93 | LIBRARY DESTINATION lib 94 | ARCHIVE DESTINATION lib 95 | ) -------------------------------------------------------------------------------- /HexViewerExten/src/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ClassFactory.h" 3 | 4 | HINSTANCE g_hInst = NULL; 5 | long g_cDllRef = 0; 6 | 7 | #pragma comment(linker, "/export:DllCanUnloadNow=DllCanUnloadNow") 8 | #pragma comment(linker, "/export:DllGetClassObject=DllGetClassObject") 9 | #pragma comment(linker, "/export:DllRegisterServer=DllRegisterServer") 10 | #pragma comment(linker, "/export:DllUnregisterServer=DllUnregisterServer") 11 | 12 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) 13 | { 14 | switch (dwReason) 15 | { 16 | case DLL_PROCESS_ATTACH: 17 | g_hInst = hModule; 18 | DisableThreadLibraryCalls(hModule); 19 | LogMessage(L"DllMain: DLL_PROCESS_ATTACH"); 20 | break; 21 | 22 | case DLL_PROCESS_DETACH: 23 | LogMessage(L"DllMain: DLL_PROCESS_DETACH"); 24 | break; 25 | } 26 | return TRUE; 27 | } 28 | 29 | extern "C" STDAPI DllCanUnloadNow(void) 30 | { 31 | LogMessage(L"DllCanUnloadNow called"); 32 | return g_cDllRef > 0 ? S_FALSE : S_OK; 33 | } 34 | 35 | extern "C" HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { 36 | LogMessage(L"DllGetClassObject called."); // Log a message 37 | 38 | HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; 39 | 40 | if (IsEqualCLSID(CLSID_HexViewer, rclsid)) 41 | { 42 | hr = E_OUTOFMEMORY; 43 | 44 | ClassFactory* pClassFactory = new ClassFactory(); 45 | if (pClassFactory) 46 | { 47 | hr = pClassFactory->QueryInterface(riid, ppv); 48 | pClassFactory->Release(); 49 | } 50 | } 51 | 52 | return hr; 53 | } 54 | 55 | extern "C" STDAPI DllRegisterServer(void) 56 | { 57 | HRESULT hr; 58 | 59 | wchar_t szModule[MAX_PATH]; 60 | if (GetModuleFileName(g_hInst, szModule, ARRAYSIZE(szModule)) == 0) 61 | { 62 | hr = HRESULT_FROM_WIN32(GetLastError()); 63 | return hr; 64 | } 65 | 66 | hr = RegisterInprocServer(szModule, CLSID_HexViewer, 67 | L"CulcShellExtContextMenuHandler.ShellEx Class", 68 | L"Apartment"); 69 | if (SUCCEEDED(hr)) 70 | { 71 | hr = RegisterShellExtContextMenuHandler(L"*", 72 | CLSID_HexViewer, 73 | L"CulcShellExtContextMenuHandler.ShellEx"); 74 | } 75 | if (SUCCEEDED(hr)) 76 | { 77 | hr = RegisterShellExtContextMenuHandler(L".lnk", 78 | CLSID_HexViewer, 79 | L"CulcShellExtContextMenuHandler.FileContextMenuExt"); 80 | } 81 | 82 | return hr; 83 | } 84 | 85 | extern "C" STDAPI DllUnregisterServer(void) 86 | { 87 | HRESULT hr = S_OK; 88 | 89 | hr = UnregisterInprocServer(CLSID_HexViewer); 90 | if (SUCCEEDED(hr)) 91 | { 92 | hr = UnregisterShellExtContextMenuHandler(L"*", CLSID_HexViewer); 93 | } 94 | if (SUCCEEDED(hr)) 95 | { 96 | hr = UnregisterShellExtContextMenuHandler(L".lnk", CLSID_HexViewer); 97 | } 98 | 99 | return hr; 100 | } -------------------------------------------------------------------------------- /HexViewer/include/ui/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef MENU_H 2 | #define MENU_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "render.h" 9 | 10 | #ifdef _WIN32 11 | #include 12 | #elif __APPLE__ 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | struct Color; 19 | struct Rect; 20 | class RenderManager; 21 | 22 | enum class MenuItemType { 23 | Normal, 24 | Separator, 25 | Submenu 26 | }; 27 | 28 | struct MenuItem { 29 | std::string label; 30 | std::string shortcut; 31 | MenuItemType type; 32 | bool enabled; 33 | bool checked; 34 | std::function callback; 35 | std::vector submenu; 36 | 37 | MenuItem(const std::string& lbl = "", MenuItemType t = MenuItemType::Normal) 38 | : label(lbl), type(t), enabled(true), checked(false) {} 39 | }; 40 | 41 | struct Menu { 42 | std::string title; 43 | std::vector items; 44 | bool visible; 45 | Rect bounds; // now Rect is known 46 | int selectedIndex; 47 | 48 | Menu(const std::string& t = "") 49 | : title(t), visible(false), selectedIndex(-1) { 50 | } 51 | }; 52 | 53 | class MenuBar { 54 | public: 55 | MenuBar(); 56 | ~MenuBar(); 57 | 58 | void addMenu(const Menu& menu); 59 | void clearMenus(); 60 | Menu* getMenu(size_t index); 61 | size_t getMenuCount() const { return menus.size(); } 62 | 63 | void setPosition(int x, int y); 64 | void setHeight(int h) { height = h; } 65 | int getHeight() const { return height; } 66 | 67 | void render(RenderManager* renderer, int windowWidth); 68 | 69 | bool handleMouseMove(int x, int y); 70 | bool handleMouseDown(int x, int y); 71 | bool handleMouseUp(int x, int y); 72 | bool handleKeyPress(int keyCode, bool ctrl, bool shift, bool alt); 73 | 74 | bool isMenuOpen() const { return openMenuIndex >= 0; } 75 | bool containsPoint(int x, int y) const; 76 | Rect getBounds(int windowWidth) const; 77 | 78 | void closeAllMenus(); 79 | 80 | private: 81 | std::vector menus; 82 | int x, y; 83 | int height; 84 | int openMenuIndex; 85 | int hoveredMenuIndex; 86 | int hoveredItemIndex; 87 | 88 | void openMenu(int menuIndex); 89 | void closeMenu(); 90 | int getMenuIndexAt(int mx, int my) const; 91 | int getMenuItemIndexAt(int menuIndex, int mx, int my) const; 92 | void calculateMenuBounds(); 93 | void executeMenuItem(MenuItem& item); 94 | 95 | void createNativeMenu(); 96 | void destroyNativeMenu(); 97 | 98 | #ifdef _WIN32 99 | HMENU hMenu; 100 | #elif __APPLE__ 101 | void* nativeMenuRef; 102 | #else 103 | #endif 104 | }; 105 | 106 | namespace MenuHelper { 107 | Menu createFileMenu( 108 | std::function onNew, 109 | std::function onOpen, 110 | std::function onSave, 111 | std::function onExit 112 | ); 113 | 114 | Menu createEditMenu( 115 | std::function onCopy, 116 | std::function onPaste, 117 | std::function onFind 118 | ); 119 | 120 | Menu createViewMenu( 121 | std::function onToggleDarkMode, 122 | std::function onZoomIn, 123 | std::function onZoomOut 124 | ); 125 | 126 | Menu createHelpMenu( 127 | std::function onAbout, 128 | std::function onDocumentation 129 | ); 130 | Menu createSearchMenu( 131 | std::function onFindReplace, 132 | std::function onGoTo 133 | ); 134 | } 135 | 136 | #endif // MENU_H 137 | -------------------------------------------------------------------------------- /HexViewer/src/ui/contextmenu.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #include 4 | 5 | HKEY ContextMenuRegistry::GetRootKey(UserRole role) { 6 | switch (role) { 7 | case UserRole::CurrentUser: 8 | return HKEY_CURRENT_USER; 9 | default: 10 | return HKEY_CURRENT_USER; 11 | } 12 | } 13 | 14 | std::wstring ContextMenuRegistry::GetRegistryPath(UserRole role) { 15 | switch (role) { 16 | case UserRole::CurrentUser: 17 | return L"Software\\Classes\\*\\shell\\HexViewer"; 18 | default: 19 | return L"Software\\Classes\\*\\shell\\HexViewer"; 20 | } 21 | } 22 | 23 | bool ContextMenuRegistry::SetRegistryValue(HKEY hKey, const wchar_t* valueName, const wchar_t* data) { 24 | LONG result = RegSetValueExW( 25 | hKey, 26 | valueName, 27 | 0, 28 | REG_SZ, 29 | (const BYTE*)data, 30 | (DWORD)((wcslen(data) + 1) * sizeof(wchar_t)) 31 | ); 32 | return result == ERROR_SUCCESS; 33 | } 34 | 35 | bool ContextMenuRegistry::DeleteRegistryKey(HKEY hRootKey, const wchar_t* subKey) { 36 | LONG result = RegDeleteTreeW(hRootKey, subKey); 37 | return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND; 38 | } 39 | 40 | bool ContextMenuRegistry::Register(const wchar_t* exePath, UserRole role) { 41 | HKEY hKey = nullptr; 42 | HKEY hCommandKey = nullptr; 43 | bool success = false; 44 | 45 | HKEY rootKey = GetRootKey(role); 46 | std::wstring registryPath = GetRegistryPath(role); 47 | 48 | LONG result = RegCreateKeyExW( 49 | rootKey, 50 | registryPath.c_str(), 51 | 0, 52 | nullptr, 53 | REG_OPTION_NON_VOLATILE, 54 | KEY_WRITE, 55 | nullptr, 56 | &hKey, 57 | nullptr 58 | ); 59 | 60 | if (result != ERROR_SUCCESS) { 61 | return false; 62 | } 63 | 64 | if (!SetRegistryValue(hKey, nullptr, L"Open with Hex Viewer")) { 65 | RegCloseKey(hKey); 66 | return false; 67 | } 68 | 69 | std::wstring iconPath = std::wstring(exePath) + L",0"; 70 | SetRegistryValue(hKey, L"Icon", iconPath.c_str()); 71 | 72 | result = RegCreateKeyExW( 73 | hKey, 74 | L"command", 75 | 0, 76 | nullptr, 77 | REG_OPTION_NON_VOLATILE, 78 | KEY_WRITE, 79 | nullptr, 80 | &hCommandKey, 81 | nullptr 82 | ); 83 | 84 | if (result == ERROR_SUCCESS) { 85 | std::wstring command = std::wstring(L"\"") + exePath + L"\" \"%1\""; 86 | success = SetRegistryValue(hCommandKey, nullptr, command.c_str()); 87 | RegCloseKey(hCommandKey); 88 | } 89 | 90 | RegCloseKey(hKey); 91 | 92 | if (success) { 93 | SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); 94 | } 95 | 96 | return success; 97 | } 98 | 99 | bool ContextMenuRegistry::Unregister(UserRole role) { 100 | HKEY rootKey = GetRootKey(role); 101 | std::wstring registryPath = GetRegistryPath(role); 102 | 103 | bool success = DeleteRegistryKey(rootKey, registryPath.c_str()); 104 | 105 | if (success) { 106 | SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); 107 | } 108 | 109 | return success; 110 | } 111 | 112 | bool ContextMenuRegistry::IsRegistered(UserRole role) { 113 | HKEY hKey = nullptr; 114 | HKEY rootKey = GetRootKey(role); 115 | std::wstring registryPath = GetRegistryPath(role); 116 | 117 | LONG result = RegOpenKeyExW( 118 | rootKey, 119 | registryPath.c_str(), 120 | 0, 121 | KEY_READ, 122 | &hKey 123 | ); 124 | 125 | if (result == ERROR_SUCCESS) { 126 | RegCloseKey(hKey); 127 | return true; 128 | } 129 | 130 | return false; 131 | } 132 | 133 | #endif // _WIN32 134 | -------------------------------------------------------------------------------- /HexViewerExten/src/Reg.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Reg.h" 3 | 4 | 5 | 6 | #pragma region Registry Helper Functions 7 | 8 | HRESULT SetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, 9 | PCWSTR pszData) 10 | { 11 | HRESULT hr; 12 | HKEY hKey = NULL; 13 | 14 | hr = HRESULT_FROM_WIN32(RegCreateKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0, 15 | NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL)); 16 | 17 | if (SUCCEEDED(hr)) 18 | { 19 | if (pszData != NULL) 20 | { 21 | DWORD cbData = lstrlen(pszData) * sizeof(*pszData); 22 | hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0, 23 | REG_SZ, reinterpret_cast(pszData), cbData)); 24 | } 25 | 26 | RegCloseKey(hKey); 27 | } 28 | 29 | return hr; 30 | } 31 | 32 | HRESULT GetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, 33 | PWSTR pszData, DWORD cbData) 34 | { 35 | HRESULT hr; 36 | HKEY hKey = NULL; 37 | 38 | hr = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0, 39 | KEY_READ, &hKey)); 40 | 41 | if (SUCCEEDED(hr)) 42 | { 43 | hr = HRESULT_FROM_WIN32(RegQueryValueEx(hKey, pszValueName, NULL, 44 | NULL, reinterpret_cast(pszData), &cbData)); 45 | 46 | RegCloseKey(hKey); 47 | } 48 | 49 | return hr; 50 | } 51 | 52 | #pragma endregion 53 | 54 | HRESULT RegisterInprocServer(PCWSTR pszModule, const CLSID& clsid, 55 | PCWSTR pszFriendlyName, PCWSTR pszThreadModel) 56 | { 57 | if (pszModule == NULL || pszThreadModel == NULL) 58 | { 59 | return E_INVALIDARG; 60 | } 61 | 62 | HRESULT hr; 63 | 64 | wchar_t szCLSID[MAX_PATH]; 65 | StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID)); 66 | 67 | wchar_t szSubkey[MAX_PATH]; 68 | 69 | hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\\%s", szCLSID); 70 | if (SUCCEEDED(hr)) 71 | { 72 | hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszFriendlyName); 73 | 74 | if (SUCCEEDED(hr)) 75 | { 76 | hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), 77 | L"CLSID\\%s\\InprocServer32", szCLSID); 78 | if (SUCCEEDED(hr)) 79 | { 80 | hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszModule); 81 | if (SUCCEEDED(hr)) 82 | { 83 | hr = SetHKCRRegistryKeyAndValue(szSubkey, 84 | L"ThreadingModel", pszThreadModel); 85 | } 86 | } 87 | } 88 | } 89 | 90 | return hr; 91 | } 92 | 93 | HRESULT UnregisterInprocServer(const CLSID& clsid) 94 | { 95 | HRESULT hr = S_OK; 96 | 97 | wchar_t szCLSID[MAX_PATH]; 98 | StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID)); 99 | 100 | wchar_t szSubkey[MAX_PATH]; 101 | 102 | hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\\%s", szCLSID); 103 | if (SUCCEEDED(hr)) 104 | { 105 | hr = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, szSubkey)); 106 | } 107 | 108 | return hr; 109 | } 110 | 111 | HRESULT RegisterShellExtContextMenuHandler( 112 | PCWSTR pszFileType, const CLSID& clsid, PCWSTR pszFriendlyName) 113 | { 114 | if (pszFileType == NULL) 115 | { 116 | return E_INVALIDARG; 117 | } 118 | 119 | HRESULT hr; 120 | 121 | wchar_t szCLSID[MAX_PATH]; 122 | StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID)); 123 | 124 | wchar_t szSubkey[MAX_PATH]; 125 | 126 | if (*pszFileType == L'.') 127 | { 128 | wchar_t szDefaultVal[260]; 129 | hr = GetHKCRRegistryKeyAndValue(pszFileType, NULL, szDefaultVal, 130 | sizeof(szDefaultVal)); 131 | 132 | if (SUCCEEDED(hr) && szDefaultVal[0] != L'\0') 133 | { 134 | pszFileType = szDefaultVal; 135 | } 136 | } 137 | 138 | hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), 139 | L"%s\\shellex\\ContextMenuHandlers\\%s", pszFileType, szCLSID); 140 | if (SUCCEEDED(hr)) 141 | { 142 | hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszFriendlyName); 143 | } 144 | 145 | return hr; 146 | } 147 | 148 | HRESULT UnregisterShellExtContextMenuHandler( 149 | PCWSTR pszFileType, const CLSID& clsid) 150 | { 151 | if (pszFileType == NULL) 152 | { 153 | return E_INVALIDARG; 154 | } 155 | 156 | HRESULT hr; 157 | 158 | wchar_t szCLSID[MAX_PATH]; 159 | StringFromGUID2(clsid, szCLSID, ARRAYSIZE(szCLSID)); 160 | 161 | wchar_t szSubkey[MAX_PATH]; 162 | 163 | if (*pszFileType == L'.') 164 | { 165 | wchar_t szDefaultVal[260]; 166 | hr = GetHKCRRegistryKeyAndValue(pszFileType, NULL, szDefaultVal, 167 | sizeof(szDefaultVal)); 168 | 169 | if (SUCCEEDED(hr) && szDefaultVal[0] != L'\0') 170 | { 171 | pszFileType = szDefaultVal; 172 | } 173 | } 174 | 175 | hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), 176 | L"%s\\shellex\\ContextMenuHandlers\\%s", pszFileType, szCLSID); 177 | if (SUCCEEDED(hr)) 178 | { 179 | hr = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_CLASSES_ROOT, szSubkey)); 180 | } 181 | 182 | return hr; 183 | } -------------------------------------------------------------------------------- /HexViewer/src/core/HexData.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | HexData::HexData() : currentBytesPerLine(16), modified(false), capstoneInitialized(false), 15 | currentArch(CS_ARCH_X86), currentMode(CS_MODE_64) { 16 | initializeCapstone(); 17 | } 18 | 19 | HexData::~HexData() { 20 | cleanupCapstone(); 21 | } 22 | 23 | bool HexData::initializeCapstone() { 24 | if (capstoneInitialized) { 25 | cleanupCapstone(); 26 | } 27 | 28 | cs_err err = cs_open(currentArch, currentMode, &csHandle); 29 | if (err != CS_ERR_OK) { 30 | capstoneInitialized = false; 31 | return false; 32 | } 33 | 34 | cs_option(csHandle, CS_OPT_DETAIL, CS_OPT_ON); 35 | cs_option(csHandle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL); 36 | 37 | capstoneInitialized = true; 38 | return true; 39 | } 40 | 41 | void HexData::cleanupCapstone() { 42 | if (capstoneInitialized) { 43 | cs_close(&csHandle); 44 | capstoneInitialized = false; 45 | } 46 | } 47 | 48 | void HexData::setArchitecture(cs_arch arch, cs_mode mode) { 49 | currentArch = arch; 50 | currentMode = mode; 51 | initializeCapstone(); 52 | 53 | if (!fileData.empty()) { 54 | regenerateHexLines(currentBytesPerLine); 55 | } 56 | } 57 | 58 | bool HexData::loadFile(const char* filepath) { 59 | std::ifstream file(filepath, std::ios::binary | std::ios::ate); 60 | if (!file.is_open()) { 61 | hexLines.clear(); 62 | hexLines.push_back("Error: Failed to open file"); 63 | return false; 64 | } 65 | 66 | std::streamsize fileSize = file.tellg(); 67 | file.seekg(0, std::ios::beg); 68 | 69 | fileData.clear(); 70 | fileData.resize(fileSize); 71 | 72 | if (!file.read(reinterpret_cast(fileData.data()), fileSize)) { 73 | hexLines.clear(); 74 | hexLines.push_back("Error: Failed to read file"); 75 | file.close(); 76 | return false; 77 | } 78 | 79 | file.close(); 80 | 81 | convertDataToHex(16); 82 | modified = false; 83 | 84 | return true; 85 | } 86 | 87 | bool HexData::saveFile(const char* filepath) { 88 | std::ofstream file(filepath, std::ios::binary); 89 | if (!file.is_open()) { 90 | return false; 91 | } 92 | 93 | file.write(reinterpret_cast(fileData.data()), fileData.size()); 94 | if (!file.good()) { 95 | file.close(); 96 | return false; 97 | } 98 | 99 | file.close(); 100 | modified = false; 101 | return true; 102 | } 103 | 104 | bool HexData::editByte(size_t offset, uint8_t newValue) { 105 | if (offset >= fileData.size()) { 106 | return false; 107 | } 108 | 109 | fileData[offset] = newValue; 110 | modified = true; 111 | 112 | regenerateHexLines(currentBytesPerLine); 113 | return true; 114 | } 115 | 116 | uint8_t HexData::getByte(size_t offset) const { 117 | if (offset >= fileData.size()) { 118 | return 0; 119 | } 120 | return fileData[offset]; 121 | } 122 | 123 | void HexData::clear() { 124 | fileData.clear(); 125 | hexLines.clear(); 126 | disassemblyLines.clear(); 127 | headerLine.clear(); 128 | modified = false; 129 | } 130 | 131 | void HexData::regenerateHexLines(int bytesPerLine) { 132 | if (!fileData.empty()) { 133 | convertDataToHex(bytesPerLine); 134 | } 135 | } 136 | 137 | void HexData::generateHeader(int bytesPerLine) { 138 | char buffer[16]; 139 | headerLine.clear(); 140 | 141 | headerLine += "Offset "; 142 | 143 | for (int i = 0; i < bytesPerLine; ++i) { 144 | snprintf(buffer, sizeof(buffer), "%02d ", i); 145 | headerLine += buffer; 146 | } 147 | 148 | headerLine += " "; 149 | 150 | headerLine += "Decoded text"; 151 | } 152 | 153 | std::string HexData::disassembleInstruction(size_t offset, int& instructionLength) { 154 | if (!capstoneInitialized || offset >= fileData.size()) { 155 | instructionLength = 1; 156 | return ""; 157 | } 158 | 159 | cs_insn* insn = nullptr; 160 | size_t remainingBytes = fileData.size() - offset; 161 | size_t bytesToDisasm = (remainingBytes < 15) ? remainingBytes : 15; 162 | 163 | size_t count = cs_disasm(csHandle, 164 | &fileData[offset], 165 | bytesToDisasm, 166 | offset, 167 | 1, 168 | &insn); 169 | 170 | if (count > 0) { 171 | instructionLength = insn[0].size; 172 | 173 | std::string mnemonic = insn[0].mnemonic; 174 | std::string op_str = insn[0].op_str; 175 | std::string full = mnemonic + (op_str[0] ? " " + op_str : ""); 176 | 177 | cs_free(insn, count); 178 | return full; 179 | } 180 | 181 | instructionLength = 1; 182 | return ""; 183 | } 184 | 185 | void HexData::generateDisassembly(int bytesPerLine) { 186 | disassemblyLines.clear(); 187 | 188 | if (fileData.empty()) { 189 | return; 190 | } 191 | 192 | for (size_t i = 0; i < fileData.size(); i += bytesPerLine) { 193 | std::string disasmLine; 194 | size_t lineOffset = i; 195 | 196 | while (lineOffset < i + bytesPerLine && lineOffset < fileData.size()) { 197 | int instrLen = 0; 198 | std::string instr = disassembleInstruction(lineOffset, instrLen); 199 | 200 | if (!instr.empty()) { 201 | if (!disasmLine.empty()) { 202 | disasmLine += "; "; 203 | } 204 | disasmLine += instr; 205 | } 206 | 207 | lineOffset += instrLen; 208 | break; 209 | } 210 | 211 | disassemblyLines.push_back(disasmLine); 212 | } 213 | } 214 | 215 | void HexData::convertDataToHex(int bytesPerLine) { 216 | hexLines.clear(); 217 | 218 | if (fileData.empty()) { 219 | hexLines.push_back("No data to display"); 220 | headerLine.clear(); 221 | return; 222 | } 223 | 224 | bytesPerLine = std::clamp(bytesPerLine, 8, 48); 225 | currentBytesPerLine = bytesPerLine; 226 | 227 | generateHeader(bytesPerLine); 228 | generateDisassembly(bytesPerLine); 229 | 230 | char buffer[512]; 231 | 232 | for (size_t i = 0; i < fileData.size(); i += bytesPerLine) { 233 | std::string line; 234 | 235 | snprintf(buffer, sizeof(buffer), "%08X ", (unsigned int)i); 236 | line += buffer; 237 | 238 | for (int j = 0; j < bytesPerLine; ++j) { 239 | if (i + j < fileData.size()) { 240 | snprintf(buffer, sizeof(buffer), "%02X ", fileData[i + j]); 241 | line += buffer; 242 | } 243 | else { 244 | line += " "; 245 | } 246 | } 247 | 248 | line += " "; 249 | 250 | for (int j = 0; j < bytesPerLine && i + j < fileData.size(); ++j) { 251 | uint8_t b = fileData[i + j]; 252 | if (b >= 32 && b <= 126) 253 | line += (char)b; 254 | else 255 | line += '.'; 256 | } 257 | 258 | line += " "; 259 | 260 | size_t lineIndex = i / bytesPerLine; 261 | if (lineIndex < disassemblyLines.size()) { 262 | line += disassemblyLines[lineIndex]; 263 | } 264 | 265 | hexLines.push_back(line); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /HexViewerExten/src/ShellEx.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ShellEx.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "resource.h" 8 | 9 | #pragma comment( lib, "comsuppwd.lib") 10 | 11 | extern HINSTANCE g_hInst; 12 | extern long g_cDllRef; 13 | 14 | #define IDM_DISPLAY 0 15 | 16 | HexViewer::HexViewer(void) :m_cRef(1), 17 | 18 | m_pszMenuText(L"Open with HexViewer"), 19 | m_pszVerb("HexViewer"), 20 | m_pwszVerb(L"HexViewer"), 21 | m_pszVerbCanonicalName("HexViewer"), 22 | m_pwszVerbCanonicalName(L"HexViewer"), 23 | m_pszVerbHelpText("Open file in HexViewer"), 24 | m_pwszVerbHelpText(L"HexViewer"), 25 | m_nFiles(0) 26 | 27 | 28 | { 29 | InterlockedIncrement(&g_cDllRef); 30 | m_hMenuBmp = LoadImage(g_hInst, MAKEINTRESOURCE(IDB_OK), 31 | IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADTRANSPARENT); 32 | if (m_hMenuBmp == NULL) { 33 | DWORD dwError = GetLastError(); 34 | LPVOID lpMsgBuf; 35 | FormatMessage( 36 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 37 | FORMAT_MESSAGE_FROM_SYSTEM | 38 | FORMAT_MESSAGE_IGNORE_INSERTS, 39 | NULL, 40 | dwError, 41 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 42 | (LPTSTR)&lpMsgBuf, 43 | 0, NULL); 44 | MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK | MB_ICONERROR); 45 | LocalFree(lpMsgBuf); 46 | } 47 | } 48 | 49 | HexViewer::~HexViewer(void) 50 | { 51 | LogMessage(L"HexViewer destructor called"); 52 | if (m_hMenuBmp) 53 | { 54 | DeleteObject(m_hMenuBmp); 55 | m_hMenuBmp = NULL; 56 | } 57 | InterlockedDecrement(&g_cDllRef); 58 | 59 | 60 | } 61 | 62 | 63 | IFACEMETHODIMP_(ULONG) HexViewer::AddRef() 64 | { 65 | return InterlockedIncrement(&m_cRef); 66 | } 67 | IFACEMETHODIMP_(ULONG) HexViewer::Release() 68 | { 69 | ULONG cRef = InterlockedDecrement(&m_cRef); 70 | if (0 == cRef) 71 | { 72 | delete this; 73 | } 74 | 75 | return cRef; 76 | } 77 | IFACEMETHODIMP HexViewer::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID) 78 | { 79 | if (NULL == pDataObj) 80 | { 81 | return E_INVALIDARG; 82 | } 83 | 84 | HRESULT hr = E_FAIL; 85 | 86 | FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; 87 | STGMEDIUM stm; 88 | 89 | if (SUCCEEDED(pDataObj->GetData(&fe, &stm))) 90 | { 91 | HDROP hDrop = static_cast(GlobalLock(stm.hGlobal)); 92 | if (hDrop != NULL) 93 | { 94 | UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); 95 | if (nFiles == 1) 96 | { 97 | if (0 != DragQueryFile(hDrop, 0, m_szSelectedFile, 98 | ARRAYSIZE(m_szSelectedFile))) 99 | { 100 | hr = S_OK; 101 | } 102 | } 103 | 104 | GlobalUnlock(stm.hGlobal); 105 | } 106 | 107 | ReleaseStgMedium(&stm); 108 | } 109 | 110 | return hr; 111 | } 112 | IFACEMETHODIMP HexViewer::QueryInterface(REFIID riid, void** ppv) 113 | { 114 | static const QITAB qit[] = 115 | { 116 | QITABENT(HexViewer, IContextMenu), 117 | QITABENT(HexViewer, IShellExtInit), 118 | { 0 }, 119 | }; 120 | return QISearch(this, qit, riid, ppv); 121 | } 122 | 123 | IFACEMETHODIMP HexViewer::QueryContextMenu( 124 | HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) 125 | { 126 | LogMessage(L"QueryContextMenu entered"); 127 | 128 | if (uFlags & CMF_DEFAULTONLY) { 129 | LogMessage(L"CMF_DEFAULTONLY set; not adding items"); 130 | return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0)); 131 | } 132 | 133 | MENUITEMINFO mii = { sizeof(mii) }; 134 | mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE /*| MIIM_BITMAP*/; 135 | mii.wID = idCmdFirst + IDM_DISPLAY; 136 | mii.fType = MFT_STRING; 137 | mii.fState = MFS_ENABLED; 138 | 139 | if (!m_pszMenuText || *m_pszMenuText == L'\0') { 140 | LogMessage(L"m_pszMenuText is empty; using fallback text"); 141 | m_pszMenuText = L"Open with HexViewer"; 142 | } 143 | 144 | size_t len = wcslen(m_pszMenuText) + 1; 145 | std::unique_ptr pszMenuTextCopy(new WCHAR[len]); 146 | wcscpy_s(pszMenuTextCopy.get(), len, m_pszMenuText); 147 | mii.dwTypeData = pszMenuTextCopy.get(); 148 | 149 | 150 | if (!InsertMenuItem(hMenu, indexMenu, TRUE, &mii)) { 151 | DWORD err = GetLastError(); 152 | LogMessage(L"InsertMenuItem (text) failed, error=" + std::to_wstring(err)); 153 | return HRESULT_FROM_WIN32(err); 154 | } 155 | LogMessage(L"Inserted menu item: Open with HexViewer"); 156 | 157 | /* 158 | MENUITEMINFO sep = { sizeof(sep) }; 159 | sep.fMask = MIIM_TYPE; 160 | sep.fType = MFT_SEPARATOR; 161 | if (!InsertMenuItem(hMenu, indexMenu + 1, TRUE, &sep)) { 162 | DWORD err = GetLastError(); 163 | LogMessage(L"InsertMenuItem (separator) failed, error=" + std::to_wstring(err)); 164 | return HRESULT_FROM_WIN32(err); 165 | } 166 | */ 167 | 168 | return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(1)); 169 | } 170 | 171 | 172 | IFACEMETHODIMP HexViewer::InvokeCommand(LPCMINVOKECOMMANDINFO pici) 173 | { 174 | LogMessage(L"InvokeCommand entered"); 175 | 176 | if (!HIWORD(pici->lpVerb)) { 177 | UINT idCmd = LOWORD(pici->lpVerb); 178 | if (idCmd != IDM_DISPLAY) { 179 | LogMessage(L"InvokeCommand: wrong idCmd"); 180 | return E_FAIL; 181 | } 182 | 183 | if (m_szSelectedFile[0] == L'\0') { 184 | LogMessage(L"InvokeCommand: no selected file"); 185 | return E_FAIL; 186 | } 187 | 188 | LogMessage(std::wstring(L"Selected file: ") + m_szSelectedFile); 189 | 190 | WCHAR szModuleDir[MAX_PATH] = {}; 191 | if (!GetModuleFileNameW(g_hInst, szModuleDir, ARRAYSIZE(szModuleDir))) { 192 | DWORD err = GetLastError(); 193 | LogMessage(L"GetModuleFileName failed, error=" + std::to_wstring(err)); 194 | return HRESULT_FROM_WIN32(err); 195 | } 196 | PathRemoveFileSpecW(szModuleDir); 197 | 198 | WCHAR szHexViewer[MAX_PATH] = {}; 199 | if (!PathCombineW(szHexViewer, szModuleDir, L"HexViewer.exe")) { 200 | LogMessage(L"PathCombine failed"); 201 | return E_FAIL; 202 | } 203 | 204 | LogMessage(std::wstring(L"Exe path: ") + szHexViewer); 205 | 206 | HINSTANCE hRes = ShellExecuteW( 207 | pici->hwnd, 208 | L"open", 209 | szHexViewer, 210 | m_szSelectedFile, 211 | NULL, 212 | SW_SHOWNORMAL 213 | ); 214 | 215 | if ((INT_PTR)hRes <= 32) { 216 | DWORD err = GetLastError(); 217 | LogMessage(L"ShellExecute failed, error=" + std::to_wstring(err)); 218 | return HRESULT_FROM_WIN32(err); 219 | } 220 | 221 | LogMessage(L"ShellExecute succeeded"); 222 | return S_OK; 223 | } 224 | 225 | LogMessage(L"InvokeCommand: verb not handled"); 226 | return E_FAIL; 227 | } 228 | 229 | 230 | 231 | IFACEMETHODIMP HexViewer::GetCommandString(UINT_PTR idCommand, 232 | UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) 233 | { 234 | HRESULT hr = E_INVALIDARG; 235 | 236 | if (idCommand == IDM_DISPLAY) 237 | { 238 | switch (uFlags) 239 | { 240 | case GCS_HELPTEXTW: 241 | hr = StringCchCopy(reinterpret_cast(pszName), cchMax, 242 | m_pwszVerbHelpText); 243 | break; 244 | 245 | case GCS_VERBW: 246 | hr = StringCchCopy(reinterpret_cast(pszName), cchMax, 247 | m_pwszVerbCanonicalName); 248 | break; 249 | 250 | default: 251 | hr = S_OK; 252 | } 253 | } 254 | 255 | 256 | return hr; 257 | } -------------------------------------------------------------------------------- /HexViewer/include/core/render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #elif __APPLE__ 11 | #include 12 | #else 13 | #include 14 | #include 15 | #endif 16 | 17 | struct Rect { 18 | int x, y, width, height; 19 | 20 | Rect(int x = 0, int y = 0, int w = 0, int h = 0) 21 | : x(x), y(y), width(w), height(h) { 22 | } 23 | 24 | bool contains(int px, int py) const { 25 | return px >= x && px < x + width && py >= y && py < y + height; 26 | } 27 | }; 28 | 29 | struct Color { 30 | uint8_t r, g, b, a; 31 | 32 | Color(uint8_t r = 255, uint8_t g = 255, uint8_t b = 255, uint8_t a = 255) 33 | : r(r), g(g), b(b), a(a) { 34 | } 35 | 36 | static Color White() { return Color(255, 255, 255); } 37 | static Color Black() { return Color(0, 0, 0); } 38 | static Color Gray(uint8_t level) { return Color(level, level, level); } 39 | static Color Green() { return Color(100, 255, 100); } 40 | static Color DarkGreen() { return Color(0, 128, 0); } 41 | static Color Blue() { return Color(100, 100, 200, 128); } 42 | static Color Yellow() { return Color(255, 255, 0); } 43 | }; 44 | 45 | struct Theme { 46 | Color windowBackground; 47 | Color textColor; 48 | Color headerColor; 49 | Color separator; 50 | Color scrollbarBg; 51 | Color scrollbarThumb; 52 | Color disassemblyColor; 53 | 54 | Color buttonNormal; 55 | Color buttonHover; 56 | Color buttonPressed; 57 | Color buttonText; 58 | Color buttonDisabled; 59 | 60 | Color controlBorder; 61 | Color controlBackground; 62 | Color controlCheck; 63 | 64 | Color menuBackground; 65 | Color menuHover; 66 | Color menuBorder; 67 | Color disabledText; 68 | 69 | static Theme Dark() { 70 | Theme t; 71 | t.windowBackground = Color(30, 30, 30); 72 | t.textColor = Color(220, 220, 220); 73 | t.headerColor = Color(100, 149, 237); 74 | t.separator = Color(70, 70, 70); 75 | t.scrollbarBg = Color(45, 45, 45); 76 | t.scrollbarThumb = Color(90, 90, 90); 77 | t.disassemblyColor = Color(144, 238, 144); 78 | 79 | t.buttonNormal = Color(50, 50, 50); 80 | t.buttonHover = Color(70, 70, 70); 81 | t.buttonPressed = Color(35, 35, 35); 82 | t.buttonText = Color(220, 220, 220); 83 | t.buttonDisabled = Color(40, 40, 40); 84 | 85 | t.controlBorder = Color(100, 100, 100); 86 | t.controlBackground = Color(45, 45, 45); 87 | t.controlCheck = Color(100, 149, 237); 88 | 89 | t.menuBackground = Color(45, 45, 48); 90 | t.menuHover = Color(62, 62, 66); 91 | t.menuBorder = Color(63, 63, 70); 92 | t.disabledText = Color(128, 128, 128); 93 | 94 | return t; 95 | } 96 | 97 | static Theme Light() { 98 | Theme t; 99 | t.windowBackground = Color(255, 255, 255); 100 | t.textColor = Color(0, 0, 0); 101 | t.headerColor = Color(0, 102, 204); 102 | t.separator = Color(200, 200, 200); 103 | t.scrollbarBg = Color(240, 240, 240); 104 | t.scrollbarThumb = Color(180, 180, 180); 105 | t.disassemblyColor = Color(0, 128, 0); 106 | 107 | t.buttonNormal = Color(240, 240, 240); 108 | t.buttonHover = Color(225, 225, 225); 109 | t.buttonPressed = Color(200, 200, 200); 110 | t.buttonText = Color(0, 0, 0); 111 | t.buttonDisabled = Color(245, 245, 245); 112 | 113 | t.controlBorder = Color(180, 180, 180); 114 | t.controlBackground = Color(255, 255, 255); 115 | t.controlCheck = Color(0, 102, 204); 116 | 117 | t.menuBackground = Color(240, 240, 240); 118 | t.menuHover = Color(225, 225, 225); 119 | t.menuBorder = Color(200, 200, 200); 120 | t.disabledText = Color(160, 160, 160); 121 | 122 | return t; 123 | } 124 | }; 125 | 126 | struct Point { 127 | int X; 128 | int Y; 129 | Point(int x = 0, int y = 0) : X(x), Y(y) {} 130 | }; 131 | 132 | struct PointF { 133 | float X; 134 | float Y; 135 | PointF(float x = 0.0f, float y = 0.0f) : X(x), Y(y) {} 136 | }; 137 | 138 | struct BytePositionInfo { 139 | long long Index; 140 | int CharacterPosition; 141 | BytePositionInfo(long long idx = 0, int charPos = 0) 142 | : Index(idx), CharacterPosition(charPos) { 143 | } 144 | }; 145 | 146 | struct LayoutMetrics { 147 | float margin = 20.0f; 148 | float headerHeight = 20.0f; 149 | float lineHeight = 20.0f; 150 | float charWidth = 9.6f; 151 | float scrollbarWidth = 20.0f; 152 | 153 | LayoutMetrics() 154 | : margin(20.0f), 155 | headerHeight(20.0f), 156 | lineHeight(20.0f), 157 | charWidth(9.6f), 158 | scrollbarWidth(20.0f) { 159 | } 160 | }; 161 | 162 | 163 | struct WidgetState { 164 | Rect rect; 165 | bool hovered; 166 | bool pressed; 167 | bool enabled; 168 | 169 | WidgetState() : hovered(false), pressed(false), enabled(true) {} 170 | WidgetState(const Rect& r) : rect(r), hovered(false), pressed(false), enabled(true) {} 171 | WidgetState(const Rect& r, bool h, bool p, bool e) : rect(r), hovered(h), pressed(p), enabled(e) {} 172 | }; 173 | 174 | 175 | #ifdef _WIN32 176 | typedef HWND NativeWindow; 177 | #elif __APPLE__ 178 | typedef void* NativeWindow; 179 | #else 180 | typedef Window NativeWindow; 181 | #endif 182 | 183 | class RenderManager { 184 | public: 185 | RenderManager(); 186 | ~RenderManager(); 187 | 188 | 189 | 190 | bool initialize(NativeWindow window); 191 | void cleanup(); 192 | void resize(int width, int height); 193 | 194 | void beginFrame(); 195 | void endFrame(); 196 | 197 | void clear(const Color& color); 198 | void drawRect(const Rect& rect, const Color& color, bool filled = true); 199 | void drawLine(int x1, int y1, int x2, int y2, const Color& color); 200 | void drawText(const std::string& text, int x, int y, const Color& color); 201 | void drawRoundedRect(const Rect& rect, float radius, const Color& color, bool filled); 202 | void drawModernButton(const WidgetState& state, const Theme& theme, const std::string& label); 203 | void drawModernCheckbox(const WidgetState& state, const Theme& theme, bool checked); 204 | void drawModernRadioButton(const WidgetState& state, const Theme& theme, bool selected); 205 | Point GetGridBytePoint(long long byteIndex); 206 | PointF GetBytePointF(long long byteIndex); 207 | PointF GetBytePointF(Point gridPoint); 208 | BytePositionInfo GetHexBytePositionInfo(Point screenPoint); 209 | void drawProgressBar(const Rect& rect, float progress, const Theme& theme); 210 | void UpdateCaret(); 211 | void DrawCaret(); 212 | long long ScreenToByteIndex(int mouseX, int mouseY); 213 | 214 | #ifdef _WIN32 215 | void drawBitmap(void* hBitmap, int width, int height, int x, int y); 216 | #elif __APPLE__ 217 | void drawImage(void* nsImage, int width, int height, int x, int y); 218 | #else 219 | void drawX11Pixmap(Pixmap pixmap, int width, int height, int x, int y); 220 | #endif 221 | 222 | 223 | void drawDropdown( 224 | const WidgetState& state, 225 | const Theme& theme, 226 | const std::string& selectedText, 227 | bool isOpen, 228 | const std::vector& items, 229 | int selectedIndex, 230 | int hoveredIndex, 231 | int scrollOffset); 232 | 233 | void renderHexViewer( 234 | const std::vector& hexLines, 235 | const std::string& headerLine, 236 | int scrollPos, 237 | int maxScrollPos, 238 | bool scrollbarHovered, 239 | bool scrollbarPressed, 240 | const Rect& scrollbarRect, 241 | const Rect& thumbRect, 242 | bool darkMode, 243 | int editingRow, 244 | int editingCol, 245 | const std::string& editBuffer, 246 | long long cursorBytePos, 247 | int cursorNibblePos, 248 | long long totalBytes); 249 | 250 | 251 | Theme getCurrentTheme() const { return currentTheme; } 252 | 253 | int getWindowWidth() const { return windowWidth; } 254 | int getWindowHeight() const { return windowHeight; } 255 | 256 | private: 257 | NativeWindow window; 258 | int windowWidth; 259 | int windowHeight; 260 | Theme currentTheme; 261 | long long _bytePos; 262 | int _byteCharacterPos; 263 | long long _startByte; 264 | long long _selectionLength; 265 | int _bytesPerLine; 266 | int _visibleLines; 267 | 268 | int _hexAreaX; 269 | int _hexAreaY; 270 | int _charWidth; 271 | int _charHeight; 272 | #ifdef _WIN32 273 | HDC hdc; 274 | HDC memDC; 275 | HBITMAP memBitmap; 276 | HBITMAP oldBitmap; 277 | HFONT font; 278 | void* pixels; 279 | BITMAPINFO bitmapInfo; 280 | #elif __APPLE__ 281 | CGContextRef context; 282 | void* backBuffer; 283 | #else 284 | Display* display; 285 | GC gc; 286 | Pixmap backBuffer; 287 | XFontStruct* fontInfo; 288 | #endif 289 | 290 | void createFont(); 291 | void destroyFont(); 292 | void setColor(const Color& color); 293 | }; 294 | -------------------------------------------------------------------------------- /HexViewer/src/ui/notification.cpp: -------------------------------------------------------------------------------- 1 | #include "notification.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace Microsoft::WRL; 21 | using namespace Microsoft::WRL::Wrappers; 22 | using namespace ABI::Windows::UI::Notifications; 23 | using namespace ABI::Windows::Data::Xml::Dom; 24 | 25 | #elif defined(__linux__) 26 | #include 27 | #endif 28 | 29 | AppNotification::AppNotification() 30 | #ifdef _WIN32 31 | : m_shortcutCreated(false) 32 | #endif 33 | { 34 | #ifdef _WIN32 35 | m_initialized = false; 36 | 37 | HRESULT hrCom = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 38 | if (FAILED(hrCom) && hrCom != RPC_E_CHANGED_MODE) { 39 | return; 40 | } 41 | 42 | HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED); 43 | if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) { 44 | m_initialized = true; 45 | } 46 | #endif 47 | } 48 | AppNotification::~AppNotification() { 49 | #ifdef _WIN32 50 | if (m_initialized) { 51 | RoUninitialize(); 52 | } 53 | #endif 54 | } 55 | 56 | AppNotification& AppNotification::GetInstance() { 57 | static AppNotification instance; 58 | return instance; 59 | } 60 | 61 | bool AppNotification::IsAvailable() { 62 | #ifdef _WIN32 63 | return true; 64 | #elif defined(__linux__) 65 | return system("which notify-send > /dev/null 2>&1") == 0; 66 | #elif defined(__APPLE__) 67 | return system("which osascript > /dev/null 2>&1") == 0; 68 | #else 69 | return false; 70 | #endif 71 | } 72 | 73 | void AppNotification::Show(const std::string& title, 74 | const std::string& message, 75 | const std::string& appId, 76 | NotificationIcon icon, 77 | int timeoutMs) { 78 | #ifdef _WIN32 79 | GetInstance().ShowWindows(title, message, appId, icon, timeoutMs); 80 | #elif defined(__linux__) 81 | GetInstance().ShowLinux(title, message, icon, timeoutMs); 82 | #elif defined(__APPLE__) 83 | GetInstance().ShowMacOS(title, message); 84 | #endif 85 | } 86 | 87 | #ifdef _WIN32 88 | 89 | bool AppNotification::EnsureShortcutExists(const std::wstring& appId) { 90 | if (m_shortcutCreated) { 91 | return true; 92 | } 93 | 94 | wchar_t exePath[MAX_PATH]; 95 | if (GetModuleFileNameW(NULL, exePath, MAX_PATH) == 0) { 96 | return false; 97 | } 98 | 99 | wchar_t startMenuPath[MAX_PATH]; 100 | if (FAILED(SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, startMenuPath))) { 101 | return false; 102 | } 103 | 104 | std::wstring shortcutPath = std::wstring(startMenuPath) + L"\\" + appId + L".lnk"; 105 | 106 | DWORD attribs = GetFileAttributesW(shortcutPath.c_str()); 107 | if (attribs != INVALID_FILE_ATTRIBUTES) { 108 | m_shortcutCreated = true; 109 | return true; 110 | } 111 | 112 | IShellLinkW* pShellLink = NULL; 113 | HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, 114 | IID_IShellLinkW, (LPVOID*)&pShellLink); 115 | 116 | if (SUCCEEDED(hr)) { 117 | pShellLink->SetPath(exePath); 118 | pShellLink->SetDescription(L"HexViewer Application"); 119 | pShellLink->SetWorkingDirectory(L""); 120 | 121 | IPropertyStore* pPropertyStore = NULL; 122 | hr = pShellLink->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropertyStore); 123 | 124 | if (SUCCEEDED(hr)) { 125 | PROPVARIANT pv; 126 | PropVariantInit(&pv); 127 | pv.vt = VT_LPWSTR; 128 | pv.pwszVal = const_cast(appId.c_str()); 129 | 130 | hr = pPropertyStore->SetValue(PKEY_AppUserModel_ID, pv); 131 | if (SUCCEEDED(hr)) { 132 | hr = pPropertyStore->Commit(); 133 | } 134 | else { 135 | } 136 | pPropertyStore->Release(); 137 | } 138 | 139 | IPersistFile* pPersistFile = NULL; 140 | hr = pShellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&pPersistFile); 141 | 142 | if (SUCCEEDED(hr)) { 143 | hr = pPersistFile->Save(shortcutPath.c_str(), TRUE); 144 | if (SUCCEEDED(hr)) { 145 | m_shortcutCreated = true; 146 | } 147 | else { 148 | } 149 | pPersistFile->Release(); 150 | } 151 | pShellLink->Release(); 152 | } 153 | else { 154 | } 155 | 156 | return m_shortcutCreated; 157 | } 158 | 159 | void AppNotification::ShowWindowsFallback(const std::string& title, 160 | const std::string& message, 161 | NotificationIcon icon) { 162 | 163 | std::string iconStr = "INFO"; 164 | switch (icon) { 165 | case NotificationIcon::Warning: iconStr = "WARNING"; break; 166 | case NotificationIcon::Error: iconStr = "ERROR"; break; 167 | default: iconStr = "INFO"; break; 168 | } 169 | 170 | } 171 | 172 | void AppNotification::ShowWindows(const std::string& title, 173 | const std::string& message, 174 | const std::string& appId, 175 | NotificationIcon icon, 176 | int timeoutMs) { 177 | 178 | if (!m_initialized) { 179 | ShowWindowsFallback(title, message, icon); 180 | return; 181 | } 182 | 183 | std::wstring wTitle(title.begin(), title.end()); 184 | std::wstring wMessage(message.begin(), message.end()); 185 | std::wstring wAppId(appId.begin(), appId.end()); 186 | 187 | if (!EnsureShortcutExists(wAppId)) { 188 | } 189 | 190 | HRESULT hr = S_OK; 191 | 192 | ComPtr toastStatics; 193 | hr = Windows::Foundation::GetActivationFactory( 194 | HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), 195 | &toastStatics); 196 | if (FAILED(hr)) { 197 | ShowWindowsFallback(title, message, icon); 198 | return; 199 | } 200 | 201 | ComPtr toastXml; 202 | hr = toastStatics->GetTemplateContent(ToastTemplateType_ToastText02, &toastXml); 203 | if (FAILED(hr)) { 204 | ShowWindowsFallback(title, message, icon); 205 | return; 206 | } 207 | 208 | ComPtr textNodes; 209 | toastXml->GetElementsByTagName(HStringReference(L"text").Get(), &textNodes); 210 | 211 | if (textNodes) { 212 | ComPtr titleNode; 213 | textNodes->Item(0, &titleNode); 214 | if (titleNode) { 215 | ComPtr titleText; 216 | ComPtr xmlDoc; 217 | titleNode->get_OwnerDocument(&xmlDoc); 218 | if (xmlDoc) { 219 | xmlDoc->CreateTextNode(HStringReference(wTitle.c_str()).Get(), &titleText); 220 | ComPtr titleTextNode; 221 | titleText.As(&titleTextNode); 222 | ComPtr appendedChild; 223 | titleNode->AppendChild(titleTextNode.Get(), &appendedChild); 224 | } 225 | } 226 | 227 | ComPtr messageNode; 228 | textNodes->Item(1, &messageNode); 229 | if (messageNode) { 230 | ComPtr messageText; 231 | ComPtr xmlDoc; 232 | messageNode->get_OwnerDocument(&xmlDoc); 233 | if (xmlDoc) { 234 | xmlDoc->CreateTextNode(HStringReference(wMessage.c_str()).Get(), &messageText); 235 | ComPtr messageTextNode; 236 | messageText.As(&messageTextNode); 237 | ComPtr appendedChild; 238 | messageNode->AppendChild(messageTextNode.Get(), &appendedChild); 239 | } 240 | } 241 | } 242 | 243 | ComPtr toastFactory; 244 | hr = Windows::Foundation::GetActivationFactory( 245 | HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), 246 | &toastFactory); 247 | if (FAILED(hr)) { 248 | ShowWindowsFallback(title, message, icon); 249 | return; 250 | } 251 | 252 | ComPtr toast; 253 | hr = toastFactory->CreateToastNotification(toastXml.Get(), &toast); 254 | if (FAILED(hr)) { 255 | ShowWindowsFallback(title, message, icon); 256 | return; 257 | } 258 | 259 | ComPtr notifier; 260 | hr = toastStatics->CreateToastNotifierWithId( 261 | HStringReference(wAppId.c_str()).Get(), 262 | ¬ifier); 263 | if (FAILED(hr)) { 264 | ShowWindowsFallback(title, message, icon); 265 | return; 266 | } 267 | 268 | hr = notifier->Show(toast.Get()); 269 | if (FAILED(hr)) { 270 | ShowWindowsFallback(title, message, icon); 271 | return; 272 | } 273 | } 274 | 275 | std::wstring AppNotification::EscapeXml(const std::wstring& str) { 276 | std::wstring result; 277 | for (wchar_t c : str) { 278 | switch (c) { 279 | case L'&': result += L"&"; break; 280 | case L'<': result += L"<"; break; 281 | case L'>': result += L">"; break; 282 | case L'"': result += L"""; break; 283 | case L'\'': result += L"'"; break; 284 | default: result += c; break; 285 | } 286 | } 287 | return result; 288 | } 289 | #endif 290 | 291 | #ifdef __linux__ 292 | void AppNotification::ShowLinux(const std::string& title, 293 | const std::string& message, 294 | NotificationIcon icon, 295 | int timeoutMs) { 296 | std::string iconStr; 297 | switch (icon) { 298 | case NotificationIcon::Info: iconStr = "dialog-information"; break; 299 | case NotificationIcon::Warning: iconStr = "dialog-warning"; break; 300 | case NotificationIcon::Error: iconStr = "dialog-error"; break; 301 | default: iconStr = "dialog-information"; break; 302 | } 303 | 304 | std::ostringstream cmd; 305 | cmd << "notify-send -t " << timeoutMs 306 | << " -i " << iconStr 307 | << " \"" << title << "\" \"" << message << "\""; 308 | system(cmd.str().c_str()); 309 | } 310 | #endif 311 | 312 | #ifdef __APPLE__ 313 | void AppNotification::ShowMacOS(const std::string& title, 314 | const std::string& message) { 315 | std::ostringstream cmd; 316 | cmd << "osascript -e 'display notification \"" 317 | << message << "\" with title \"" << title << "\"'"; 318 | system(cmd.str().c_str()); 319 | } 320 | #endif 321 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(HexViewer VERSION 1.0.0 LANGUAGES CXX C) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) 11 | endif() 12 | 13 | if(MSVC) 14 | set(CMAKE_C_FLAGS_RELEASE "/O1 /GL /Gy /DNDEBUG" CACHE STRING "" FORCE) 15 | set(CMAKE_CXX_FLAGS_RELEASE "/O1 /GL /Gy /DNDEBUG" CACHE STRING "" FORCE) 16 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO" CACHE STRING "" FORCE) 17 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO" CACHE STRING "" FORCE) 18 | set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "/LTCG" CACHE STRING "" FORCE) 19 | endif() 20 | 21 | set(CAPSTONE_DIR "") 22 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/XCapstone/") 23 | set(CAPSTONE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/XCapstone/") 24 | elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Capstone") 25 | set(CAPSTONE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Capstone") 26 | elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/capstone") 27 | set(CAPSTONE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/capstone") 28 | endif() 29 | 30 | if(CAPSTONE_DIR) 31 | message(STATUS "Building Capstone from source in ${CAPSTONE_DIR}") 32 | if(MSVC) 33 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 34 | endif() 35 | set(CAPSTONE_BUILD_STATIC ON CACHE BOOL "Build static library") 36 | set(CAPSTONE_BUILD_SHARED OFF CACHE BOOL "Build shared library") 37 | set(CAPSTONE_BUILD_TESTS OFF CACHE BOOL "Build tests") 38 | set(CAPSTONE_BUILD_CSTOOL OFF CACHE BOOL "Build cstool") 39 | set(CAPSTONE_ARCHITECTURE_DEFAULT ON CACHE BOOL "Enable all architectures") 40 | 41 | add_subdirectory(${CAPSTONE_DIR} ${CMAKE_BINARY_DIR}/capstone) 42 | 43 | set(CAPSTONE_FOUND TRUE) 44 | set(CAPSTONE_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/XCapstone/3rdparty/Capstone/src/include") 45 | set(CAPSTONE_LIBRARIES capstone) 46 | else() 47 | find_package(PkgConfig) 48 | if(PkgConfig_FOUND) 49 | pkg_check_modules(CAPSTONE capstone) 50 | endif() 51 | if(NOT CAPSTONE_FOUND) 52 | find_path(CAPSTONE_INCLUDE_DIR capstone/capstone.h PATHS /usr/include /usr/local/include) 53 | find_library(CAPSTONE_LIBRARY NAMES capstone PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu) 54 | if(CAPSTONE_INCLUDE_DIR AND CAPSTONE_LIBRARY) 55 | set(CAPSTONE_FOUND TRUE) 56 | set(CAPSTONE_INCLUDE_DIRS ${CAPSTONE_INCLUDE_DIR}) 57 | set(CAPSTONE_LIBRARIES ${CAPSTONE_LIBRARY}) 58 | endif() 59 | endif() 60 | if(NOT CAPSTONE_FOUND) 61 | message(FATAL_ERROR "Capstone library not found! Place source in ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/Capstone/src or install libcapstone-dev") 62 | endif() 63 | endif() 64 | 65 | set(SOURCES 66 | HexViewer/src/HexViewer.cpp 67 | HexViewer/src/core/render.cpp 68 | HexViewer/src/core/HexData.cpp 69 | HexViewer/src/ui/menu.cpp 70 | HexViewer/src/ui/options.cpp 71 | HexViewer/src/ui/searchdialog.cpp 72 | HexViewer/src/ui/about.cpp 73 | HexViewer/src/ui/contextmenu.cpp 74 | HexViewer/src/ui/notification.cpp 75 | HexViewer/src/system/update.cpp 76 | ) 77 | 78 | set(HEADERS 79 | HexViewer/include/core/HexData.h 80 | HexViewer/include/core/render.h 81 | HexViewer/include/core/imageloader.h 82 | HexViewer/include/ui/menu.h 83 | HexViewer/include/ui/options.h 84 | HexViewer/include/ui/searchdialog.h 85 | HexViewer/include/ui/about.h 86 | HexViewer/include/ui/contextmenu.h 87 | HexViewer/include/ui/darkmode.h 88 | HexViewer/include/system/update.h 89 | HexViewer/include/system/language.h 90 | HexViewer/include/system/resource.h 91 | ) 92 | 93 | 94 | if(WIN32) 95 | set(RESOURCE_FILES HexViewer/resources/ui/HexViewer.rc) 96 | add_executable(HexViewer ${SOURCES} ${HEADERS} ${RESOURCE_FILES}) 97 | 98 | elseif(APPLE) 99 | add_executable(HexViewer ${SOURCES} ${HEADERS}) 100 | 101 | else() 102 | find_program(CMAKE_OBJCOPY objcopy REQUIRED) 103 | 104 | set(RESOURCE_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/resources/images/about.png) 105 | set(RESOURCE_OUTPUT ${CMAKE_BINARY_DIR}/about.o) 106 | 107 | add_custom_command( 108 | OUTPUT ${RESOURCE_OUTPUT} 109 | COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/resources/images && 110 | ${CMAKE_OBJCOPY} -I binary -O elf64-x86-64 -B i386 111 | --redefine-sym _binary_about_png_start=_binary_about_png_start 112 | --redefine-sym _binary_about_png_end=_binary_about_png_end 113 | --redefine-sym _binary_about_png_size=_binary_about_png_size 114 | about.png ${RESOURCE_OUTPUT} 115 | DEPENDS ${RESOURCE_INPUT} 116 | COMMENT "Embedding about.png into object file" 117 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/resources 118 | ) 119 | 120 | add_executable(HexViewer ${SOURCES} ${HEADERS} ${RESOURCE_OUTPUT}) 121 | endif() 122 | 123 | if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerShell") 124 | message(STATUS "Found HexViewerShell directory - building DLL") 125 | set(HEXVIEWERSHELL_SOURCE HexViewerShell/src/dllmain.cpp) 126 | 127 | add_library(HexViewerShell SHARED ${HEXVIEWERSHELL_SOURCE}) 128 | 129 | target_compile_definitions(HexViewerShell PRIVATE UNICODE _UNICODE) 130 | 131 | target_link_libraries(HexViewerShell PRIVATE 132 | shlwapi 133 | windowsapp 134 | ole32 135 | shell32 136 | ) 137 | 138 | if(MSVC) 139 | set_target_properties(HexViewerShell PROPERTIES 140 | LINK_FLAGS "/MACHINE:X64" 141 | ) 142 | endif() 143 | 144 | set_target_properties(HexViewerShell PROPERTIES 145 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 146 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 147 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 148 | ) 149 | 150 | install(TARGETS HexViewerShell RUNTIME DESTINATION bin) 151 | else() 152 | message(STATUS "HexViewerShell directory not found - skipping DLL build") 153 | endif() 154 | 155 | if(WIN32) 156 | set_target_properties(HexViewer PROPERTIES WIN32_EXECUTABLE TRUE) 157 | endif() 158 | 159 | if(WIN32) 160 | target_compile_definitions(HexViewer PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN) 161 | target_link_libraries(HexViewer PRIVATE ${CAPSTONE_LIBRARIES} gdi32 user32 shell32 comdlg32 ole32 runtimeobject dwmapi wininet) 162 | elseif(APPLE) 163 | target_compile_definitions(HexViewer PRIVATE __APPLE__) 164 | find_library(COCOA_FRAMEWORK Cocoa) 165 | find_library(COREGRAPHICS_FRAMEWORK CoreGraphics) 166 | find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) 167 | target_link_libraries(HexViewer PRIVATE ${CAPSTONE_LIBRARIES} 168 | ${COCOA_FRAMEWORK} ${COREGRAPHICS_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK}) 169 | else() 170 | find_package(X11 REQUIRED) 171 | target_compile_definitions(HexViewer PRIVATE __linux__) 172 | target_link_libraries(HexViewer PRIVATE ${CAPSTONE_LIBRARIES} ${X11_LIBRARIES} pthread dl) 173 | target_include_directories(HexViewer PRIVATE ${X11_INCLUDE_DIR}) 174 | endif() 175 | 176 | target_include_directories(HexViewer PRIVATE 177 | ${CAPSTONE_INCLUDE_DIRS} 178 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/include/core 179 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/include/ui 180 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/include/system 181 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/resources/images 182 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/src/core 183 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/src/ui 184 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewer/src/system 185 | ) 186 | 187 | if(MSVC) 188 | target_compile_options(HexViewer PRIVATE /W3) 189 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 190 | target_compile_options(HexViewer PRIVATE /O1 /GL /Gy) 191 | target_link_options(HexViewer PRIVATE /LTCG /OPT:REF /OPT:ICF) 192 | set_property(TARGET HexViewer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") 193 | else() 194 | set_property(TARGET HexViewer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") 195 | endif() 196 | else() 197 | target_compile_options(HexViewer PRIVATE -Wall -Wextra -Wpedantic) 198 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 199 | target_compile_options(HexViewer PRIVATE -Os -s) 200 | endif() 201 | endif() 202 | 203 | if(MSVC AND CMAKE_BUILD_TYPE STREQUAL "Release") 204 | string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") 205 | string(REPLACE "/DEBUG" "" CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 206 | endif() 207 | 208 | set_target_properties(HexViewer PROPERTIES 209 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 210 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 211 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 212 | ) 213 | 214 | if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten") 215 | message(STATUS "Found HexViewerExten directory (Windows only)") 216 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/CMakeLists.txt") 217 | add_subdirectory(HexViewerExten) 218 | else() 219 | file(GLOB_RECURSE HEXVIEWER_EXTERN_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/src/*.[ch]pp" "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/src/*.[ch]") 220 | file(GLOB_RECURSE HEXVIEWER_EXTERN_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/include/*.[ch]pp" "${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/include/*.[ch]") 221 | if(HEXVIEWER_EXTERN_SOURCES) 222 | add_executable(HexViewerExten ${HEXVIEWER_EXTERN_SOURCES} ${HEXVIEWER_EXTERN_HEADERS}) 223 | 224 | target_compile_definitions(HexViewerExten PRIVATE 225 | UNICODE _UNICODE WIN32_LEAN_AND_MEAN 226 | DIESHELLEXTENSION_EXPORTS _WINDOWS _USRDLL 227 | ) 228 | 229 | target_link_libraries(HexViewerExten PRIVATE ${CAPSTONE_LIBRARIES} gdi32 user32 shell32 comdlg32) 230 | target_include_directories(HexViewerExten PRIVATE 231 | ${CAPSTONE_INCLUDE_DIRS} 232 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/include 233 | ${CMAKE_CURRENT_SOURCE_DIR}/HexViewerExten/src 234 | ) 235 | if(MSVC) 236 | target_compile_options(HexViewerExten PRIVATE /W3) 237 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 238 | target_compile_options(HexViewerExten PRIVATE /O1 /GL /Gy) 239 | target_link_options(HexViewerExten PRIVATE 240 | /LTCG 241 | /OPT:REF 242 | /OPT:ICF 243 | ) 244 | set_property(TARGET HexViewerExten PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") 245 | else() 246 | set_property(TARGET HexViewerExten PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") 247 | endif() 248 | endif() 249 | set_target_properties(HexViewerExten PROPERTIES 250 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 251 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 252 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 253 | ) 254 | install(TARGETS HexViewerExten RUNTIME DESTINATION bin) 255 | endif() 256 | endif() 257 | else() 258 | message(STATUS "HexViewerExten directory not found - skipping build") 259 | endif() 260 | 261 | install(TARGETS HexViewer RUNTIME DESTINATION bin) 262 | 263 | set(CPACK_PACKAGE_NAME "HexViewer") 264 | set(CPACK_PACKAGE_VENDOR "HexViewer") 265 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cross-platform hex editor with disassembly") 266 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 267 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "HexViewer") 268 | 269 | if(WIN32) 270 | set(CPACK_GENERATOR "NSIS;ZIP") 271 | elseif(APPLE) 272 | set(CPACK_GENERATOR "DragNDrop") 273 | else() 274 | set(CPACK_GENERATOR "TGZ;DEB") 275 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "DevX-Cipher") 276 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libx11-6") 277 | endif() 278 | 279 | include(CPack) -------------------------------------------------------------------------------- /HexViewer/src/ui/menu.cpp: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | #include "render.h" 3 | #include 4 | 5 | #ifdef _WIN32 6 | #include 7 | #define DEBUG_OUTPUT(msg) OutputDebugStringA(msg) 8 | #else 9 | #include 10 | #define DEBUG_OUTPUT(msg) fprintf(stderr, "%s", msg) 11 | #endif 12 | 13 | MenuBar::MenuBar() 14 | : x(0), y(0), height(24), openMenuIndex(-1), hoveredMenuIndex(-1), hoveredItemIndex(-1) 15 | #ifdef _WIN32 16 | , hMenu(nullptr) 17 | #elif __APPLE__ 18 | , nativeMenuRef(nullptr) 19 | #endif 20 | { 21 | } 22 | 23 | MenuBar::~MenuBar() { 24 | destroyNativeMenu(); 25 | } 26 | 27 | void MenuBar::addMenu(const Menu& menu) { 28 | menus.push_back(menu); 29 | calculateMenuBounds(); 30 | } 31 | 32 | void MenuBar::clearMenus() { 33 | menus.clear(); 34 | openMenuIndex = -1; 35 | hoveredMenuIndex = -1; 36 | hoveredItemIndex = -1; 37 | } 38 | 39 | Menu* MenuBar::getMenu(size_t index) { 40 | if (index < menus.size()) { 41 | return &menus[index]; 42 | } 43 | return nullptr; 44 | } 45 | 46 | void MenuBar::setPosition(int px, int py) { 47 | x = px; 48 | y = py; 49 | calculateMenuBounds(); 50 | } 51 | 52 | void MenuBar::render(RenderManager* renderer, int windowWidth) { 53 | if (!renderer) return; 54 | 55 | Theme theme = renderer->getCurrentTheme(); 56 | 57 | Rect menuBarRect(x, y, windowWidth, height); 58 | renderer->drawRect(menuBarRect, theme.menuBackground, true); 59 | 60 | renderer->drawLine(x, y + height - 1, windowWidth, y + height - 1, theme.separator); 61 | 62 | int currentX = x + 10; 63 | for (size_t i = 0; i < menus.size(); i++) { 64 | Menu& menu = menus[i]; 65 | 66 | int menuWidth = menu.title.length() * 8 + 20; 67 | menu.bounds = Rect(currentX, y, menuWidth, height); 68 | 69 | bool isHovered = (int)i == hoveredMenuIndex; 70 | bool isOpen = (int)i == openMenuIndex; 71 | 72 | if (isHovered || isOpen) { 73 | renderer->drawRect(menu.bounds, theme.menuHover, true); 74 | } 75 | 76 | int textX = currentX + 10; 77 | int textY = y + (height - 16) / 2; 78 | renderer->drawText(menu.title, textX, textY, theme.textColor); 79 | 80 | currentX += menuWidth; 81 | } 82 | 83 | if (openMenuIndex >= 0 && openMenuIndex < (int)menus.size()) { 84 | Menu& menu = menus[openMenuIndex]; 85 | 86 | int dropdownX = menu.bounds.x; 87 | int dropdownY = menu.bounds.y + menu.bounds.height; 88 | int maxWidth = 200; 89 | 90 | int dropdownHeight = 0; 91 | for (const auto& item : menu.items) { 92 | if (item.type == MenuItemType::Separator) { 93 | dropdownHeight += 8; 94 | } 95 | else { 96 | dropdownHeight += 24; 97 | } 98 | } 99 | 100 | Rect shadowRect(dropdownX + 2, dropdownY + 2, maxWidth, dropdownHeight); 101 | renderer->drawRect(shadowRect, Color(0, 0, 0, 50), true); 102 | 103 | Rect dropdownRect(dropdownX, dropdownY, maxWidth, dropdownHeight); 104 | renderer->drawRect(dropdownRect, theme.menuBackground, true); 105 | renderer->drawRect(dropdownRect, theme.menuBorder, false); 106 | 107 | int itemY = dropdownY; 108 | for (size_t i = 0; i < menu.items.size(); i++) { 109 | const MenuItem& item = menu.items[i]; 110 | 111 | if (item.type == MenuItemType::Separator) { 112 | int sepY = itemY + 4; 113 | renderer->drawLine(dropdownX + 5, sepY, 114 | dropdownX + maxWidth - 5, sepY, 115 | theme.separator); 116 | itemY += 8; 117 | } 118 | else { 119 | Rect itemRect(dropdownX, itemY, maxWidth, 24); 120 | if ((int)i == hoveredItemIndex && item.enabled) { 121 | renderer->drawRect(itemRect, theme.menuHover, true); 122 | } 123 | 124 | if (item.checked) { 125 | renderer->drawText("✓", dropdownX + 5, itemY + 4, 126 | item.enabled ? theme.textColor : theme.disabledText); 127 | } 128 | 129 | Color textColor = item.enabled ? theme.textColor : theme.disabledText; 130 | renderer->drawText(item.label, dropdownX + 25, itemY + 4, textColor); 131 | 132 | if (!item.shortcut.empty()) { 133 | int shortcutX = dropdownX + maxWidth - item.shortcut.length() * 8 - 10; 134 | renderer->drawText(item.shortcut, shortcutX, itemY + 4, theme.disabledText); 135 | } 136 | 137 | if (item.type == MenuItemType::Submenu && !item.submenu.empty()) { 138 | renderer->drawText(">", dropdownX + maxWidth - 20, itemY + 4, textColor); 139 | } 140 | 141 | itemY += 24; 142 | } 143 | } 144 | } 145 | } 146 | 147 | bool MenuBar::handleMouseMove(int mx, int my) { 148 | int oldHoveredMenu = hoveredMenuIndex; 149 | hoveredMenuIndex = getMenuIndexAt(mx, my); 150 | 151 | if (openMenuIndex >= 0) { 152 | hoveredItemIndex = getMenuItemIndexAt(openMenuIndex, mx, my); 153 | 154 | if (hoveredMenuIndex >= 0 && hoveredMenuIndex != openMenuIndex) { 155 | openMenu(hoveredMenuIndex); 156 | } 157 | 158 | return true; // Consume event if menu is open 159 | } 160 | 161 | return oldHoveredMenu != hoveredMenuIndex; 162 | } 163 | 164 | bool MenuBar::handleMouseDown(int mx, int my) { 165 | int menuIndex = getMenuIndexAt(mx, my); 166 | 167 | if (menuIndex >= 0) { 168 | if (openMenuIndex == menuIndex) { 169 | closeMenu(); 170 | } 171 | else { 172 | openMenu(menuIndex); 173 | } 174 | return true; 175 | } 176 | 177 | if (openMenuIndex >= 0) { 178 | int itemIndex = getMenuItemIndexAt(openMenuIndex, mx, my); 179 | if (itemIndex >= 0) { 180 | Menu& menu = menus[openMenuIndex]; 181 | if (itemIndex < (int)menu.items.size()) { 182 | MenuItem& item = menu.items[itemIndex]; 183 | if (item.enabled && item.type == MenuItemType::Normal) { 184 | executeMenuItem(item); 185 | closeMenu(); 186 | return true; 187 | } 188 | } 189 | } 190 | 191 | closeMenu(); 192 | return true; 193 | } 194 | 195 | return false; 196 | } 197 | 198 | bool MenuBar::handleMouseUp(int mx, int my) { 199 | return openMenuIndex >= 0; 200 | } 201 | 202 | bool MenuBar::handleKeyPress(int keyCode, bool ctrl, bool shift, bool alt) { 203 | if (openMenuIndex >= 0) { 204 | if (keyCode == 27) { // ESC 205 | closeMenu(); 206 | return true; 207 | } 208 | 209 | Menu& menu = menus[openMenuIndex]; 210 | 211 | if (keyCode == 38) { // UP 212 | do { 213 | hoveredItemIndex--; 214 | if (hoveredItemIndex < 0) hoveredItemIndex = menu.items.size() - 1; 215 | } while (hoveredItemIndex >= 0 && 216 | menu.items[hoveredItemIndex].type == MenuItemType::Separator); 217 | return true; 218 | } 219 | 220 | if (keyCode == 40) { // DOWN 221 | do { 222 | hoveredItemIndex++; 223 | if (hoveredItemIndex >= (int)menu.items.size()) hoveredItemIndex = 0; 224 | } while (hoveredItemIndex < (int)menu.items.size() && 225 | menu.items[hoveredItemIndex].type == MenuItemType::Separator); 226 | return true; 227 | } 228 | 229 | if (keyCode == 13) { // ENTER 230 | if (hoveredItemIndex >= 0 && hoveredItemIndex < (int)menu.items.size()) { 231 | MenuItem& item = menu.items[hoveredItemIndex]; 232 | if (item.enabled && item.type == MenuItemType::Normal) { 233 | executeMenuItem(item); 234 | closeMenu(); 235 | return true; 236 | } 237 | } 238 | } 239 | } 240 | 241 | return false; 242 | } 243 | 244 | bool MenuBar::containsPoint(int px, int py) const { 245 | if (py >= y && py < y + height) { 246 | return true; 247 | } 248 | 249 | if (openMenuIndex >= 0 && openMenuIndex < (int)menus.size()) { 250 | const Menu& menu = menus[openMenuIndex]; 251 | int dropdownY = y + height; 252 | 253 | int dropdownHeight = 0; 254 | for (const auto& item : menu.items) { 255 | dropdownHeight += (item.type == MenuItemType::Separator) ? 8 : 24; 256 | } 257 | 258 | if (px >= menu.bounds.x && px < menu.bounds.x + 200 && 259 | py >= dropdownY && py < dropdownY + dropdownHeight) { 260 | return true; 261 | } 262 | } 263 | 264 | return false; 265 | } 266 | 267 | Rect MenuBar::getBounds(int windowWidth) const { 268 | return Rect(x, y, windowWidth, height); 269 | } 270 | 271 | void MenuBar::closeAllMenus() { 272 | closeMenu(); 273 | } 274 | 275 | void MenuBar::openMenu(int menuIndex) { 276 | if (menuIndex >= 0 && menuIndex < (int)menus.size()) { 277 | openMenuIndex = menuIndex; 278 | hoveredItemIndex = -1; 279 | 280 | std::string debugMsg = "Opening menu: " + std::to_string(menuIndex) + "\n"; 281 | DEBUG_OUTPUT(debugMsg.c_str()); 282 | } 283 | } 284 | 285 | void MenuBar::closeMenu() { 286 | openMenuIndex = -1; 287 | hoveredItemIndex = -1; 288 | } 289 | 290 | int MenuBar::getMenuIndexAt(int mx, int my) const { 291 | if (my < y || my >= y + height) { 292 | return -1; 293 | } 294 | 295 | for (size_t i = 0; i < menus.size(); i++) { 296 | const Menu& menu = menus[i]; 297 | if (mx >= menu.bounds.x && mx < menu.bounds.x + menu.bounds.width) { 298 | return (int)i; 299 | } 300 | } 301 | 302 | return -1; 303 | } 304 | 305 | int MenuBar::getMenuItemIndexAt(int menuIndex, int mx, int my) const { 306 | if (menuIndex < 0 || menuIndex >= (int)menus.size()) { 307 | return -1; 308 | } 309 | 310 | const Menu& menu = menus[menuIndex]; 311 | int dropdownX = menu.bounds.x; 312 | int dropdownY = y + height; 313 | 314 | if (mx < dropdownX || mx >= dropdownX + 200) { 315 | return -1; 316 | } 317 | 318 | int itemY = dropdownY; 319 | for (size_t i = 0; i < menu.items.size(); i++) { 320 | const MenuItem& item = menu.items[i]; 321 | int itemHeight = (item.type == MenuItemType::Separator) ? 8 : 24; 322 | 323 | if (my >= itemY && my < itemY + itemHeight) { 324 | return (int)i; 325 | } 326 | 327 | itemY += itemHeight; 328 | } 329 | 330 | return -1; 331 | } 332 | 333 | void MenuBar::calculateMenuBounds() { 334 | int currentX = x + 10; 335 | for (auto& menu : menus) { 336 | int menuWidth = menu.title.length() * 8 + 20; 337 | menu.bounds = Rect(currentX, y, menuWidth, height); 338 | currentX += menuWidth; 339 | } 340 | } 341 | 342 | void MenuBar::executeMenuItem(MenuItem& item) { 343 | if (item.callback) { 344 | item.callback(); 345 | } 346 | } 347 | 348 | void MenuBar::createNativeMenu() { 349 | #ifdef _WIN32 350 | #elif __APPLE__ 351 | #endif 352 | } 353 | 354 | void MenuBar::destroyNativeMenu() { 355 | #ifdef _WIN32 356 | if (hMenu) { 357 | DestroyMenu(hMenu); 358 | hMenu = nullptr; 359 | } 360 | #endif 361 | } 362 | 363 | namespace MenuHelper { 364 | Menu createFileMenu( 365 | std::function onNew, 366 | std::function onOpen, 367 | std::function onSave, 368 | std::function onExit) 369 | { 370 | Menu menu("File"); 371 | 372 | MenuItem newItem("New", MenuItemType::Normal); 373 | newItem.shortcut = "Ctrl+N"; 374 | newItem.callback = onNew; 375 | menu.items.push_back(newItem); 376 | 377 | MenuItem openItem("Open...", MenuItemType::Normal); 378 | openItem.shortcut = "Ctrl+O"; 379 | openItem.callback = onOpen; 380 | menu.items.push_back(openItem); 381 | 382 | MenuItem saveItem("Save", MenuItemType::Normal); 383 | saveItem.shortcut = "Ctrl+S"; 384 | saveItem.callback = onSave; 385 | menu.items.push_back(saveItem); 386 | 387 | menu.items.push_back(MenuItem("", MenuItemType::Separator)); 388 | 389 | MenuItem exitItem("Exit", MenuItemType::Normal); 390 | exitItem.shortcut = "Alt+F4"; 391 | exitItem.callback = onExit; 392 | menu.items.push_back(exitItem); 393 | 394 | return menu; 395 | } 396 | 397 | Menu createSearchMenu( 398 | std::function onFindReplace, 399 | std::function onGoTo) 400 | { 401 | Menu menu("Search"); 402 | 403 | MenuItem findReplaceItem("Find and Replace...", MenuItemType::Normal); 404 | findReplaceItem.shortcut = "Ctrl+F"; 405 | findReplaceItem.callback = onFindReplace; 406 | menu.items.push_back(findReplaceItem); 407 | 408 | MenuItem goToItem("Go To...", MenuItemType::Normal); 409 | goToItem.shortcut = "Ctrl+G"; 410 | goToItem.callback = onGoTo; 411 | menu.items.push_back(goToItem); 412 | 413 | return menu; 414 | } 415 | 416 | Menu createEditMenu( 417 | std::function onCopy, 418 | std::function onPaste, 419 | std::function onFind) 420 | { 421 | Menu menu("Edit"); 422 | 423 | MenuItem copyItem("Copy", MenuItemType::Normal); 424 | copyItem.shortcut = "Ctrl+C"; 425 | copyItem.callback = onCopy; 426 | menu.items.push_back(copyItem); 427 | 428 | MenuItem pasteItem("Paste", MenuItemType::Normal); 429 | pasteItem.shortcut = "Ctrl+V"; 430 | pasteItem.callback = onPaste; 431 | menu.items.push_back(pasteItem); 432 | 433 | menu.items.push_back(MenuItem("", MenuItemType::Separator)); 434 | 435 | MenuItem findItem("Find...", MenuItemType::Normal); 436 | findItem.shortcut = "Ctrl+F"; 437 | findItem.callback = onFind; 438 | menu.items.push_back(findItem); 439 | 440 | return menu; 441 | } 442 | 443 | Menu createViewMenu( 444 | std::function onToggleDarkMode, 445 | std::function onZoomIn, 446 | std::function onZoomOut) 447 | { 448 | Menu menu("View"); 449 | 450 | MenuItem darkModeItem("Dark Mode", MenuItemType::Normal); 451 | darkModeItem.callback = onToggleDarkMode; 452 | menu.items.push_back(darkModeItem); 453 | 454 | menu.items.push_back(MenuItem("", MenuItemType::Separator)); 455 | 456 | MenuItem zoomInItem("Zoom In", MenuItemType::Normal); 457 | zoomInItem.shortcut = "Ctrl++"; 458 | 459 | MenuItem zoomOutItem("Zoom Out", MenuItemType::Normal); 460 | zoomOutItem.shortcut = "Ctrl+-"; 461 | zoomOutItem.callback = onZoomOut; 462 | menu.items.push_back(zoomOutItem); 463 | 464 | return menu; 465 | } 466 | 467 | Menu createHelpMenu( 468 | std::function onAbout, 469 | std::function onDocumentation) 470 | { 471 | Menu menu("Help"); 472 | 473 | MenuItem docItem("Documentation", MenuItemType::Normal); 474 | docItem.shortcut = "F1"; 475 | docItem.callback = onDocumentation; 476 | menu.items.push_back(docItem); 477 | 478 | menu.items.push_back(MenuItem("", MenuItemType::Separator)); 479 | 480 | MenuItem aboutItem("About", MenuItemType::Normal); 481 | aboutItem.callback = onAbout; 482 | menu.items.push_back(aboutItem); 483 | 484 | return menu; 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /HexViewer/include/core/imageloader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | #include 8 | #endif 9 | #ifdef __APPLE__ 10 | #import 11 | #import 12 | #endif 13 | #ifdef __linux__ 14 | #include 15 | #include 16 | extern "C" { 17 | extern const unsigned char _binary_about_png_start[]; 18 | extern const unsigned char _binary_about_png_end[]; 19 | } 20 | #endif 21 | 22 | class ImageLoader { 23 | private: 24 | struct ByteVector { 25 | uint8_t* data; 26 | size_t length; 27 | size_t capacity; 28 | }; 29 | 30 | static bool bytevector_init(ByteVector* vec, size_t initial_capacity) { 31 | vec->data = (uint8_t*)malloc(initial_capacity); 32 | if (!vec->data) return false; 33 | vec->length = 0; 34 | vec->capacity = initial_capacity; 35 | return true; 36 | } 37 | 38 | static bool bytevector_push(ByteVector* vec, uint8_t byte) { 39 | if (vec->length >= vec->capacity) { 40 | size_t new_capacity = vec->capacity * 2; 41 | if (new_capacity < 1024) new_capacity = 1024; 42 | uint8_t* new_data = (uint8_t*)realloc(vec->data, new_capacity); 43 | if (!new_data) return false; 44 | vec->data = new_data; 45 | vec->capacity = new_capacity; 46 | } 47 | vec->data[vec->length++] = byte; 48 | return true; 49 | } 50 | 51 | static void bytevector_free(ByteVector* vec) { 52 | if (vec->data) { 53 | free(vec->data); 54 | vec->data = nullptr; 55 | } 56 | vec->length = 0; 57 | vec->capacity = 0; 58 | } 59 | 60 | struct BitStream { 61 | const uint8_t* data; 62 | size_t size; 63 | size_t byte_pos; 64 | uint32_t bit_buffer; 65 | int bits_in_buffer; 66 | }; 67 | 68 | static void bitstream_init(BitStream* bs, const uint8_t* data, size_t size) { 69 | bs->data = data; 70 | bs->size = size; 71 | bs->byte_pos = 0; 72 | bs->bit_buffer = 0; 73 | bs->bits_in_buffer = 0; 74 | } 75 | 76 | static int bitstream_read_bits(BitStream* bs, int n) { 77 | while (bs->bits_in_buffer < n) { 78 | if (bs->byte_pos >= bs->size) return -1; 79 | bs->bit_buffer |= (uint32_t)bs->data[bs->byte_pos++] << bs->bits_in_buffer; 80 | bs->bits_in_buffer += 8; 81 | } 82 | int result = bs->bit_buffer & ((1 << n) - 1); 83 | bs->bit_buffer >>= n; 84 | bs->bits_in_buffer -= n; 85 | return result; 86 | } 87 | 88 | static int bitstream_read_byte_aligned(BitStream* bs) { 89 | bs->bit_buffer = 0; 90 | bs->bits_in_buffer = 0; 91 | if (bs->byte_pos >= bs->size) return -1; 92 | return bs->data[bs->byte_pos++]; 93 | } 94 | 95 | static void bitstream_align_to_byte(BitStream* bs) { 96 | bs->bit_buffer = 0; 97 | bs->bits_in_buffer = 0; 98 | } 99 | 100 | struct HuffmanTable { 101 | int max_code[16]; 102 | int offset[16]; 103 | uint16_t symbols[288]; 104 | }; 105 | 106 | static void huffman_build(HuffmanTable* table, const uint8_t* lengths, int n) { 107 | int bl_count[16] = { 0 }; 108 | for (int i = 0; i < n; i++) { 109 | if (lengths[i] > 0 && lengths[i] < 16) bl_count[lengths[i]]++; 110 | } 111 | 112 | int code = 0; 113 | bl_count[0] = 0; 114 | int next_code[16] = { 0 }; 115 | for (int bits = 1; bits < 16; bits++) { 116 | code = (code + bl_count[bits - 1]) << 1; 117 | next_code[bits] = code; 118 | } 119 | 120 | for (int bits = 0; bits < 16; bits++) { 121 | table->max_code[bits] = -1; 122 | table->offset[bits] = 0; 123 | } 124 | 125 | int sym_idx = 0; 126 | for (int bits = 1; bits < 16; bits++) { 127 | table->offset[bits] = sym_idx - next_code[bits]; 128 | for (int i = 0; i < n; i++) { 129 | if (lengths[i] == bits) { 130 | table->symbols[sym_idx++] = i; 131 | table->max_code[bits] = next_code[bits]++; 132 | } 133 | } 134 | } 135 | } 136 | 137 | static int huffman_decode(const HuffmanTable* table, BitStream* bs) { 138 | int code = 0; 139 | for (int len = 1; len < 16; len++) { 140 | int bit = bitstream_read_bits(bs, 1); 141 | if (bit < 0) return -1; 142 | code = (code << 1) | bit; 143 | if (code <= table->max_code[len] && table->max_code[len] >= 0) { 144 | return table->symbols[table->offset[len] + code]; 145 | } 146 | } 147 | return -1; 148 | } 149 | 150 | static uint32_t adler32(const uint8_t* data, size_t len) { 151 | uint32_t a = 1, b = 0; 152 | const uint32_t MOD_ADLER = 65521; 153 | for (size_t i = 0; i < len; i++) { 154 | a = (a + data[i]) % MOD_ADLER; 155 | b = (b + a) % MOD_ADLER; 156 | } 157 | return (b << 16) | a; 158 | } 159 | 160 | static constexpr uint16_t length_base[29] = { 161 | 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31, 162 | 35,43,51,59,67,83,99,115,131,163,195,227,258 163 | }; 164 | static constexpr uint8_t length_extra[29] = { 165 | 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 166 | 3,3,3,3,4,4,4,4,5,5,5,5,0 167 | }; 168 | static constexpr uint16_t dist_base[30] = { 169 | 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 170 | 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577 171 | }; 172 | static constexpr uint8_t dist_extra[30] = { 173 | 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6, 174 | 7,7,8,8,9,9,10,10,11,11,12,12,13,13 175 | }; 176 | 177 | static void init_fixed_tables(uint8_t* lit_lengths, uint8_t* dist_lengths) { 178 | for (int i = 0; i <= 143; i++) lit_lengths[i] = 8; 179 | for (int i = 144; i <= 255; i++) lit_lengths[i] = 9; 180 | for (int i = 256; i <= 279; i++) lit_lengths[i] = 7; 181 | for (int i = 280; i <= 287; i++) lit_lengths[i] = 8; 182 | for (int i = 0; i < 32; i++) dist_lengths[i] = 5; 183 | } 184 | 185 | static int decode_dynamic_tables(BitStream* bs, uint8_t* lit_lengths, uint8_t* dist_lengths) { 186 | int hlit = bitstream_read_bits(bs, 5); 187 | if (hlit < 0) return -1; hlit += 257; 188 | int hdist = bitstream_read_bits(bs, 5); 189 | if (hdist < 0) return -1; hdist += 1; 190 | int hclen = bitstream_read_bits(bs, 4); 191 | if (hclen < 0) return -1; hclen += 4; 192 | 193 | const uint8_t cl_order[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; 194 | uint8_t cl_lengths[19] = { 0 }; 195 | for (int i = 0; i < hclen; i++) { 196 | int len = bitstream_read_bits(bs, 3); 197 | if (len < 0) return -1; 198 | cl_lengths[cl_order[i]] = (uint8_t)len; 199 | } 200 | 201 | HuffmanTable cl_table; 202 | huffman_build(&cl_table, cl_lengths, 19); 203 | 204 | uint8_t all_lengths[320] = { 0 }; 205 | int idx = 0, total = hlit + hdist; 206 | 207 | while (idx < total) { 208 | int sym = huffman_decode(&cl_table, bs); 209 | if (sym < 0) return -1; 210 | if (sym < 16) all_lengths[idx++] = (uint8_t)sym; 211 | else if (sym == 16) { 212 | if (idx == 0) return -1; 213 | int repeat = bitstream_read_bits(bs, 2); 214 | if (repeat < 0) return -1; 215 | repeat += 3; 216 | uint8_t val = all_lengths[idx - 1]; 217 | while (repeat-- > 0 && idx < total) all_lengths[idx++] = val; 218 | } 219 | else if (sym == 17) { 220 | int repeat = bitstream_read_bits(bs, 3); 221 | if (repeat < 0) return -1; 222 | repeat += 3; 223 | while (repeat-- > 0 && idx < total) all_lengths[idx++] = 0; 224 | } 225 | else if (sym == 18) { 226 | int repeat = bitstream_read_bits(bs, 7); 227 | if (repeat < 0) return -1; 228 | repeat += 11; 229 | while (repeat-- > 0 && idx < total) all_lengths[idx++] = 0; 230 | } 231 | else return -1; 232 | } 233 | 234 | for (int i = 0; i < hlit; i++) lit_lengths[i] = all_lengths[i]; 235 | for (int i = 0; i < hdist; i++) dist_lengths[i] = all_lengths[hlit + i]; 236 | 237 | return 0; 238 | } 239 | 240 | static int decode_block(BitStream* bs, ByteVector* output, const uint8_t* lit_lengths, const uint8_t* dist_lengths) { 241 | HuffmanTable lit_table, dist_table; 242 | huffman_build(&lit_table, lit_lengths, 288); 243 | huffman_build(&dist_table, dist_lengths, 32); 244 | 245 | while (1) { 246 | int symbol = huffman_decode(&lit_table, bs); 247 | if (symbol < 0) return -1; 248 | if (symbol < 256) { 249 | if (!bytevector_push(output, (uint8_t)symbol)) return -1; 250 | } 251 | else if (symbol == 256) return 0; 252 | else if (symbol <= 285) { 253 | int len_code = symbol - 257; 254 | if (len_code >= 29) return -1; 255 | 256 | int length = length_base[len_code]; 257 | int extra_bits = length_extra[len_code]; 258 | if (extra_bits > 0) { 259 | int extra = bitstream_read_bits(bs, extra_bits); 260 | if (extra < 0) return -1; 261 | length += extra; 262 | } 263 | 264 | int dist_code = huffman_decode(&dist_table, bs); 265 | if (dist_code < 0 || dist_code >= 30) return -1; 266 | int distance = dist_base[dist_code]; 267 | extra_bits = dist_extra[dist_code]; 268 | if (extra_bits > 0) { 269 | int extra = bitstream_read_bits(bs, extra_bits); 270 | if (extra < 0) return -1; 271 | distance += extra; 272 | } 273 | 274 | if (distance > (int)output->length || distance <= 0) return -1; 275 | size_t copy_pos = output->length - distance; 276 | for (int i = 0; i < length; i++) { 277 | if (!bytevector_push(output, output->data[copy_pos + i])) return -1; 278 | } 279 | } 280 | else return -1; 281 | } 282 | } 283 | 284 | static int zlib_decompress_deflate(const uint8_t* input, size_t size, 285 | ByteVector* output, size_t* bytes_consumed) { 286 | BitStream bs; 287 | bitstream_init(&bs, input, size); 288 | int is_final = 0; 289 | 290 | while (!is_final) { 291 | is_final = bitstream_read_bits(&bs, 1); 292 | if (is_final < 0) return -1; 293 | 294 | int type = bitstream_read_bits(&bs, 2); 295 | if (type < 0) return -1; 296 | 297 | if (type == 0) { 298 | bitstream_align_to_byte(&bs); 299 | int len_lo = bitstream_read_byte_aligned(&bs); 300 | int len_hi = bitstream_read_byte_aligned(&bs); 301 | int nlen_lo = bitstream_read_byte_aligned(&bs); 302 | int nlen_hi = bitstream_read_byte_aligned(&bs); 303 | if (len_lo < 0 || len_hi < 0 || nlen_lo < 0 || nlen_hi < 0) return -1; 304 | uint16_t len = (uint16_t)(len_lo | (len_hi << 8)); 305 | uint16_t nlen = (uint16_t)(nlen_lo | (nlen_hi << 8)); 306 | if (len != (uint16_t)~nlen) return -1; 307 | for (int i = 0; i < len; i++) { 308 | int b = bitstream_read_byte_aligned(&bs); 309 | if (b < 0) return -1; 310 | if (!bytevector_push(output, (uint8_t)b)) return -1; 311 | } 312 | } 313 | else if (type == 1) { 314 | uint8_t lit[288], dist[32]; 315 | init_fixed_tables(lit, dist); 316 | if (decode_block(&bs, output, lit, dist) < 0) return -1; 317 | } 318 | else if (type == 2) { 319 | uint8_t lit[288] = { 0 }, dist[32] = { 0 }; 320 | if (decode_dynamic_tables(&bs, lit, dist) < 0) return -1; 321 | if (decode_block(&bs, output, lit, dist) < 0) return -1; 322 | } 323 | else { 324 | return -1; 325 | } 326 | } 327 | 328 | if (bytes_consumed) *bytes_consumed = bs.byte_pos; 329 | return 0; 330 | } 331 | 332 | static int zlib_decompress(const uint8_t* input, size_t input_size, ByteVector* output) { 333 | if (input_size < 6) return -1; 334 | uint8_t cmf = input[0], flg = input[1]; 335 | if ((cmf & 0x0F) != 8) return -1; 336 | if (((cmf << 8) + flg) % 31 != 0) return -1; 337 | if (flg & 0x20) return -1; 338 | 339 | if (input_size < 6) return -1; 340 | const uint8_t* deflate_data = input + 2; 341 | size_t deflate_size = input_size - 6; 342 | size_t consumed = 0; 343 | 344 | if (zlib_decompress_deflate(deflate_data, deflate_size, output, &consumed) < 0) 345 | return -1; 346 | 347 | if (input_size >= 6 + 4) { 348 | uint32_t stored_checksum = 349 | ((uint32_t)input[input_size - 4] << 24) | 350 | ((uint32_t)input[input_size - 3] << 16) | 351 | ((uint32_t)input[input_size - 2] << 8) | 352 | ((uint32_t)input[input_size - 1]); 353 | uint32_t computed = adler32(output->data, output->length); 354 | if (stored_checksum != computed) return -1; 355 | } 356 | 357 | return 0; 358 | } 359 | 360 | static uint32_t ReadU32BE(const std::vector& data, size_t pos) { 361 | return (uint32_t(data[pos]) << 24) | (uint32_t(data[pos + 1]) << 16) | 362 | (uint32_t(data[pos + 2]) << 8) | uint32_t(data[pos + 3]); 363 | } 364 | 365 | static bool InflateZlib(const std::vector& compressed, std::vector& out) { 366 | ByteVector output; 367 | if (!bytevector_init(&output, compressed.size() * 4)) return false; 368 | 369 | int result = zlib_decompress(compressed.data(), compressed.size(), &output); 370 | 371 | if (result == 0) { 372 | out.assign(output.data, output.data + output.length); 373 | } 374 | 375 | bytevector_free(&output); 376 | return (result == 0); 377 | } 378 | 379 | static uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) { 380 | int p = a + b - c; 381 | int pa = abs(p - a); 382 | int pb = abs(p - b); 383 | int pc = abs(p - c); 384 | if (pa <= pb && pa <= pc) return a; 385 | if (pb <= pc) return b; 386 | return c; 387 | } 388 | 389 | static bool UnfilterAndConvert(const std::vector& data, std::vector& rgba, 390 | uint32_t width, uint32_t height, uint8_t colorType, uint8_t bitDepth) { 391 | if (bitDepth != 8) return false; 392 | uint32_t bytesPerPixel = 0; 393 | bool hasAlpha = false; 394 | 395 | switch (colorType) { 396 | case 0: bytesPerPixel = 1; break; 397 | case 2: bytesPerPixel = 3; break; 398 | case 4: bytesPerPixel = 2; hasAlpha = true; break; 399 | case 6: bytesPerPixel = 4; hasAlpha = true; break; 400 | default: return false; 401 | } 402 | 403 | uint32_t stride = width * bytesPerPixel + 1; 404 | if (data.size() < stride * height) return false; 405 | 406 | rgba.resize(width * height * 4); 407 | std::vector prevRow(width * bytesPerPixel, 0); 408 | 409 | for (uint32_t y = 0; y < height; y++) { 410 | uint8_t filter = data[y * stride]; 411 | const uint8_t* row = &data[y * stride + 1]; 412 | std::vector currRow(width * bytesPerPixel); 413 | 414 | for (uint32_t x = 0; x < width * bytesPerPixel; x++) { 415 | uint8_t a = (x >= bytesPerPixel) ? currRow[x - bytesPerPixel] : 0; 416 | uint8_t b = prevRow[x]; 417 | uint8_t c = (x >= bytesPerPixel) ? prevRow[x - bytesPerPixel] : 0; 418 | 419 | switch (filter) { 420 | case 0: currRow[x] = row[x]; break; 421 | case 1: currRow[x] = row[x] + a; break; 422 | case 2: currRow[x] = row[x] + b; break; 423 | case 3: currRow[x] = row[x] + ((a + b) >> 1); break; 424 | case 4: currRow[x] = row[x] + PaethPredictor(a, b, c); break; 425 | default: return false; 426 | } 427 | } 428 | 429 | for (uint32_t x = 0; x < width; x++) { 430 | uint32_t outPos = (y * width + x) * 4; 431 | uint32_t inPos = x * bytesPerPixel; 432 | 433 | if (colorType == 0) { 434 | rgba[outPos] = rgba[outPos + 1] = rgba[outPos + 2] = currRow[inPos]; 435 | rgba[outPos + 3] = 255; 436 | } 437 | else if (colorType == 2) { 438 | rgba[outPos] = currRow[inPos]; 439 | rgba[outPos + 1] = currRow[inPos + 1]; 440 | rgba[outPos + 2] = currRow[inPos + 2]; 441 | rgba[outPos + 3] = 255; 442 | } 443 | else if (colorType == 4) { 444 | rgba[outPos] = rgba[outPos + 1] = rgba[outPos + 2] = currRow[inPos]; 445 | rgba[outPos + 3] = currRow[inPos + 1]; 446 | } 447 | else if (colorType == 6) { 448 | rgba[outPos] = currRow[inPos]; 449 | rgba[outPos + 1] = currRow[inPos + 1]; 450 | rgba[outPos + 2] = currRow[inPos + 2]; 451 | rgba[outPos + 3] = currRow[inPos + 3]; 452 | } 453 | } 454 | 455 | prevRow = currRow; 456 | } 457 | 458 | return true; 459 | } 460 | 461 | static bool DecodePNG(const std::vector& pngData, std::vector& rgba, 462 | uint32_t& width, uint32_t& height) { 463 | if (pngData.size() < 8) return false; 464 | 465 | const uint8_t sig[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; 466 | if (memcmp(pngData.data(), sig, 8) != 0) return false; 467 | 468 | size_t pos = 8; 469 | std::vector idat; 470 | uint8_t colorType = 0; 471 | uint8_t bitDepth = 0; 472 | width = height = 0; 473 | 474 | while (pos + 12 <= pngData.size()) { 475 | uint32_t len = ReadU32BE(pngData, pos); 476 | pos += 4; 477 | if (pos + 4 + len + 4 > pngData.size()) break; 478 | 479 | std::string type(reinterpret_cast(&pngData[pos]), 4); 480 | pos += 4; 481 | 482 | if (type == "IHDR" && len >= 13) { 483 | width = ReadU32BE(pngData, pos); 484 | height = ReadU32BE(pngData, pos + 4); 485 | bitDepth = pngData[pos + 8]; 486 | colorType = pngData[pos + 9]; 487 | } 488 | else if (type == "IDAT") { 489 | idat.insert(idat.end(), pngData.begin() + pos, pngData.begin() + pos + len); 490 | } 491 | else if (type == "IEND") { 492 | break; 493 | } 494 | 495 | pos += len + 4; // Skip data + CRC 496 | } 497 | 498 | if (width == 0 || height == 0 || idat.empty()) return false; 499 | 500 | std::vector decompressed; 501 | if (!InflateZlib(idat, decompressed)) return false; 502 | 503 | return UnfilterAndConvert(decompressed, rgba, width, height, colorType, bitDepth); 504 | } 505 | 506 | public: 507 | static bool LoadPNG(const char* name, std::vector& outData) { 508 | outData.clear(); 509 | #ifdef _WIN32 510 | HMODULE hModule = GetModuleHandle(nullptr); 511 | if (!hModule) return false; 512 | 513 | bool isIntResource = (reinterpret_cast(name) < 0x10000); 514 | 515 | HRSRC hRes = nullptr; 516 | if (isIntResource) { 517 | hRes = FindResourceW(hModule, reinterpret_cast(name), RT_RCDATA); 518 | } 519 | else { 520 | hRes = FindResourceA(hModule, name, "PNG"); 521 | } 522 | 523 | if (!hRes) return false; 524 | HGLOBAL hMem = LoadResource(hModule, hRes); 525 | if (!hMem) return false; 526 | DWORD size = SizeofResource(hModule, hRes); 527 | void* ptr = LockResource(hMem); 528 | if (!ptr || size == 0) return false; 529 | outData.assign(static_cast(ptr), static_cast(ptr) + size); 530 | return true; 531 | #endif 532 | #ifdef __APPLE__ 533 | @autoreleasepool{ 534 | NSString * nsName = [NSString stringWithUTF8String : name]; 535 | NSString* base = [nsName stringByDeletingPathExtension]; 536 | NSString* ext = [nsName pathExtension]; 537 | NSBundle* bundle = [NSBundle mainBundle]; 538 | NSString* path = [bundle pathForResource : base ofType : ext]; 539 | if (!path) return false; 540 | NSData* data = [NSData dataWithContentsOfFile : path]; 541 | if (!data) return false; 542 | outData.resize([data length]); 543 | memcpy(outData.data(),[data bytes],[data length]); 544 | return true; 545 | } 546 | #endif 547 | #ifdef __linux__ 548 | size_t size = _binary_about_png_end - _binary_about_png_start; 549 | 550 | if (size > 0) { 551 | outData.assign(_binary_about_png_start, _binary_about_png_end); 552 | return true; 553 | } 554 | return false; 555 | #endif 556 | } 557 | 558 | static bool LoadPNGDecoded(const char* name, std::vector& rgbaData, 559 | int& width, int& height) { 560 | uint32_t w, h; 561 | std::vector pngData; 562 | if (!LoadPNG(name, pngData)) return false; 563 | if (!DecodePNG(pngData, rgbaData, w, h)) return false; 564 | width = static_cast(w); 565 | height = static_cast(h); 566 | return true; 567 | } 568 | 569 | static bool LoadPNGDecoded(const char* name, std::vector& rgbaData, 570 | uint32_t& width, uint32_t& height) { 571 | std::vector pngData; 572 | if (!LoadPNG(name, pngData)) return false; 573 | return DecodePNG(pngData, rgbaData, width, height); 574 | } 575 | 576 | #ifdef _WIN32 577 | static HBITMAP CreateHBITMAP(const std::vector& rgbaData, 578 | int width, int height, bool premultiplyAlpha = true) { 579 | if (width <= 0 || height <= 0) return nullptr; 580 | if (rgbaData.size() != static_cast(width * height * 4)) return nullptr; 581 | 582 | BITMAPV5HEADER bmi = {}; 583 | bmi.bV5Size = sizeof(BITMAPV5HEADER); 584 | bmi.bV5Width = width; 585 | bmi.bV5Height = -height; // top-down 586 | bmi.bV5Planes = 1; 587 | bmi.bV5BitCount = 32; 588 | bmi.bV5Compression = BI_BITFIELDS; 589 | bmi.bV5RedMask = 0x00FF0000; 590 | bmi.bV5GreenMask = 0x0000FF00; 591 | bmi.bV5BlueMask = 0x000000FF; 592 | bmi.bV5AlphaMask = 0xFF000000; 593 | 594 | void* bits = nullptr; 595 | HDC hdc = GetDC(nullptr); 596 | HBITMAP hBitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &bits, nullptr, 0); 597 | ReleaseDC(nullptr, hdc); 598 | 599 | if (hBitmap && bits) { 600 | uint8_t* dest = static_cast(bits); 601 | 602 | for (size_t i = 0; i < rgbaData.size(); i += 4) { 603 | uint8_t r = rgbaData[i]; 604 | uint8_t g = rgbaData[i + 1]; 605 | uint8_t b = rgbaData[i + 2]; 606 | uint8_t a = rgbaData[i + 3]; 607 | 608 | if (premultiplyAlpha && a < 255) { 609 | r = (r * a) / 255; 610 | g = (g * a) / 255; 611 | b = (b * a) / 255; 612 | } 613 | 614 | dest[i] = b; 615 | dest[i + 1] = g; 616 | dest[i + 2] = r; 617 | dest[i + 3] = a; 618 | } 619 | } 620 | 621 | return hBitmap; 622 | } 623 | 624 | static void DrawTransparentBitmap(HDC hdc, HBITMAP hBitmap, int x, int y, int width, int height) { 625 | BITMAP bm; 626 | GetObject(hBitmap, sizeof(BITMAP), &bm); 627 | 628 | HDC hdcSrc = CreateCompatibleDC(hdc); 629 | HDC hdcDst = CreateCompatibleDC(hdc); 630 | 631 | BITMAPINFO bmi = {}; 632 | bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 633 | bmi.bmiHeader.biWidth = width; 634 | bmi.bmiHeader.biHeight = -height; // Top-down 635 | bmi.bmiHeader.biPlanes = 1; 636 | bmi.bmiHeader.biBitCount = 32; 637 | bmi.bmiHeader.biCompression = BI_RGB; 638 | 639 | uint32_t* pDstBits = nullptr; 640 | HBITMAP hDstBitmap = CreateDIBSection(hdcDst, &bmi, DIB_RGB_COLORS, (void**)&pDstBits, nullptr, 0); 641 | 642 | uint32_t* pSrcBits = nullptr; 643 | HBITMAP hSrcBitmap = CreateDIBSection(hdcSrc, &bmi, DIB_RGB_COLORS, (void**)&pSrcBits, nullptr, 0); 644 | 645 | if (!pDstBits || !pSrcBits) { 646 | if (hDstBitmap) DeleteObject(hDstBitmap); 647 | if (hSrcBitmap) DeleteObject(hSrcBitmap); 648 | DeleteDC(hdcSrc); 649 | DeleteDC(hdcDst); 650 | return; 651 | } 652 | 653 | HBITMAP hOldSrc = (HBITMAP)SelectObject(hdcSrc, hSrcBitmap); 654 | HBITMAP hOldDst = (HBITMAP)SelectObject(hdcDst, hDstBitmap); 655 | 656 | HDC hdcTemp = CreateCompatibleDC(hdc); 657 | HBITMAP hOldTemp = (HBITMAP)SelectObject(hdcTemp, hBitmap); 658 | StretchBlt(hdcSrc, 0, 0, width, height, hdcTemp, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); 659 | SelectObject(hdcTemp, hOldTemp); 660 | DeleteDC(hdcTemp); 661 | 662 | BitBlt(hdcDst, 0, 0, width, height, hdc, x, y, SRCCOPY); 663 | 664 | for (int py = 0; py < height; py++) { 665 | for (int px = 0; px < width; px++) { 666 | int idx = py * width + px; 667 | 668 | uint32_t src = pSrcBits[idx]; 669 | uint32_t dst = pDstBits[idx]; 670 | 671 | uint8_t srcA = (src >> 24) & 0xFF; 672 | uint8_t srcR = (src >> 16) & 0xFF; 673 | uint8_t srcG = (src >> 8) & 0xFF; 674 | uint8_t srcB = src & 0xFF; 675 | 676 | uint8_t dstR = (dst >> 16) & 0xFF; 677 | uint8_t dstG = (dst >> 8) & 0xFF; 678 | uint8_t dstB = dst & 0xFF; 679 | 680 | uint8_t outR = (srcR * srcA + dstR * (255 - srcA)) / 255; 681 | uint8_t outG = (srcG * srcA + dstG * (255 - srcA)) / 255; 682 | uint8_t outB = (srcB * srcA + dstB * (255 - srcA)) / 255; 683 | 684 | pDstBits[idx] = (0xFF << 24) | (outR << 16) | (outG << 8) | outB; 685 | } 686 | } 687 | 688 | BitBlt(hdc, x, y, width, height, hdcDst, 0, 0, SRCCOPY); 689 | 690 | SelectObject(hdcSrc, hOldSrc); 691 | SelectObject(hdcDst, hOldDst); 692 | DeleteObject(hSrcBitmap); 693 | DeleteObject(hDstBitmap); 694 | DeleteDC(hdcSrc); 695 | DeleteDC(hdcDst); 696 | } 697 | #endif 698 | }; 699 | -------------------------------------------------------------------------------- /HexViewer/src/ui/options.cpp: -------------------------------------------------------------------------------- 1 | #include "options.h" 2 | #include "render.h" 3 | #include "contextmenu.h" 4 | 5 | #ifdef _WIN32 6 | #include 7 | #include 8 | #include 9 | #include 10 | #elif __APPLE__ 11 | #include 12 | #include 13 | #else 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #endif 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | struct OptionsDialogData { 27 | AppOptions tempOptions; 28 | AppOptions* originalOptions; 29 | RenderManager* renderer; 30 | NativeWindow window; 31 | bool dialogResult; 32 | bool running; 33 | 34 | int mouseX; 35 | int mouseY; 36 | bool mouseDown; 37 | 38 | int hoveredWidget; 39 | int pressedWidget; 40 | 41 | bool dropdownOpen; 42 | int dropdownScrollOffset; 43 | int hoveredDropdownItem; 44 | std::vector languages; 45 | int selectedLanguage; 46 | 47 | #ifdef __linux__ 48 | Display* display; 49 | Atom wmDeleteWindow; 50 | #endif 51 | 52 | OptionsDialogData() 53 | : mouseX(0), mouseY(0), mouseDown(false), 54 | hoveredWidget(-1), pressedWidget(-1), 55 | renderer(nullptr), 56 | #ifdef _WIN32 57 | window(nullptr), 58 | #elif defined(__APPLE__) 59 | window(nullptr), 60 | #elif defined(__linux__) 61 | window(0), 62 | #endif 63 | dialogResult(false), running(true), originalOptions(nullptr), 64 | dropdownOpen(false), hoveredDropdownItem(-1), selectedLanguage(0), 65 | dropdownScrollOffset(0) 66 | { 67 | #ifdef __linux__ 68 | display = nullptr; 69 | wmDeleteWindow = 0; 70 | #endif 71 | languages = { "English", "Spanish", "French", "German", "Japanese", "Chinese", "Russian", "Italian", "Portuguese", "Korean", "Dutch", "Polish", "Turkish", "Swedish", "Arabic", "Hindi", "Czech", "Greek", "Danish", "Norwegian", "Finnish", "Vietnamese"}; 72 | } 73 | }; 74 | 75 | void DetectNative() { 76 | #ifdef BUILD_NATIVE 77 | g_isNative = true; 78 | #else 79 | #ifdef _WIN32 80 | UINT32 length = 0; 81 | LONG result = GetPackageFullName(nullptr, &length, nullptr); 82 | if (result == ERROR_SUCCESS || result == ERROR_INSUFFICIENT_BUFFER) { 83 | g_isNative = true; 84 | g_isMsix = true; 85 | return; 86 | } 87 | 88 | wchar_t exePath[MAX_PATH]; 89 | if (GetModuleFileNameW(nullptr, exePath, MAX_PATH) > 0) { 90 | std::filesystem::path path(exePath); 91 | std::wstring parentLower = path.parent_path().wstring(); 92 | std::transform(parentLower.begin(), parentLower.end(), parentLower.begin(), 93 | [](wchar_t c) { return std::towlower(c); }); 94 | 95 | if (parentLower.find(L"program files") != std::wstring::npos) { 96 | g_isNative = true; 97 | } 98 | else { 99 | g_isNative = false; 100 | } 101 | } 102 | else { 103 | g_isNative = false; 104 | } 105 | #elif __APPLE__ 106 | char path[PATH_MAX]; 107 | uint32_t size = sizeof(path); 108 | if (_NSGetExecutablePath(path, &size) == 0) { 109 | std::filesystem::path exePath(path); 110 | if (exePath.parent_path().string().find("/Applications") != std::string::npos) { 111 | g_isNative = true; 112 | } 113 | else { 114 | g_isNative = false; 115 | } 116 | } 117 | else { 118 | g_isNative = false; 119 | } 120 | 121 | #else // Linux / other Unix 122 | char buf[PATH_MAX]; 123 | ssize_t count = readlink("/proc/self/exe", buf, sizeof(buf)); 124 | if (count != -1) { 125 | std::filesystem::path exePath(std::string(buf, count)); 126 | std::string parent = exePath.parent_path().string(); 127 | if (parent.find("/usr/") == 0) { 128 | g_isNative = true; 129 | } 130 | else { 131 | g_isNative = false; 132 | } 133 | } 134 | else { 135 | g_isNative = false; 136 | } 137 | #endif 138 | #endif 139 | } 140 | 141 | std::filesystem::path GetConfigPath() { 142 | #ifdef _WIN32 143 | wchar_t exePath[MAX_PATH]; 144 | GetModuleFileNameW(nullptr, exePath, MAX_PATH); 145 | std::filesystem::path exeDir(exePath); 146 | exeDir = exeDir.parent_path(); 147 | 148 | std::wstring exeDirLower = exeDir.wstring(); 149 | std::transform(exeDirLower.begin(), exeDirLower.end(), exeDirLower.begin(), 150 | [](wchar_t c) { return std::towlower(c); }); 151 | if (exeDirLower.find(L"program files") == std::wstring::npos && !g_isNative) { 152 | return exeDir / "hexviewer_config.ini"; 153 | } 154 | 155 | wchar_t localAppData[MAX_PATH]; 156 | DWORD len = GetEnvironmentVariableW(L"LOCALAPPDATA", localAppData, MAX_PATH); 157 | if (len > 0 && len < MAX_PATH) { 158 | return std::filesystem::path(localAppData) / "HexViewer" / "hexviewer_config.ini"; 159 | } 160 | 161 | return exeDir / "hexviewer_config.ini"; 162 | #else 163 | const char* home = getenv("HOME"); 164 | if (home) { 165 | return std::filesystem::path(home) / ".config" / "HexViewer" / "hexviewer_config.ini"; 166 | } 167 | return "hexviewer_config.ini"; // fallback 168 | #endif 169 | } 170 | 171 | void SaveOptionsToFile(const AppOptions& options) { 172 | std::filesystem::path configPath = GetConfigPath(); 173 | 174 | std::error_code ec; 175 | std::filesystem::create_directories(configPath.parent_path(), ec); 176 | 177 | std::ofstream file(configPath); 178 | if (file.is_open()) { 179 | file << "darkMode=" << (options.darkMode ? "1" : "0") << "\n"; 180 | file << "bytesPerLine=" << options.defaultBytesPerLine << "\n"; 181 | file << "autoReload=" << (options.autoReload ? "1" : "0") << "\n"; 182 | file << "language=" << options.language << "\n"; 183 | file.close(); 184 | } 185 | } 186 | 187 | void LoadOptionsFromFile(AppOptions& options) { 188 | std::filesystem::path configPath = GetConfigPath(); 189 | 190 | std::ifstream file(configPath); 191 | if (file.is_open()) { 192 | std::string line; 193 | while (std::getline(file, line)) { 194 | size_t pos = line.find('='); 195 | if (pos != std::string::npos) { 196 | std::string key = line.substr(0, pos); 197 | std::string value = line.substr(pos + 1); 198 | 199 | if (key == "darkMode") options.darkMode = (value == "1"); 200 | else if (key == "bytesPerLine") options.defaultBytesPerLine = std::stoi(value); 201 | else if (key == "autoReload") options.autoReload = (value == "1"); 202 | else if (key == "language") options.language = value; 203 | } 204 | } 205 | file.close(); 206 | } 207 | } 208 | 209 | static OptionsDialogData* g_dialogData = nullptr; 210 | 211 | bool IsPointInRect(int x, int y, const Rect& rect) { 212 | return x >= rect.x && x <= rect.x + rect.width && 213 | y >= rect.y && y <= rect.y + rect.height; 214 | } 215 | 216 | void RenderOptionsDialog(OptionsDialogData* data, int windowWidth, int windowHeight) { 217 | if (!data || !data->renderer) return; 218 | 219 | data->renderer->beginFrame(); 220 | 221 | Theme theme = data->tempOptions.darkMode ? Theme::Dark() : Theme::Light(); 222 | data->renderer->clear(theme.windowBackground); 223 | 224 | int margin = 20; 225 | int y = margin; 226 | int controlHeight = 25; 227 | int spacing = 10; 228 | 229 | data->renderer->drawText(Translations::T("Options").c_str(), margin, y, theme.headerColor); 230 | y += 35; 231 | 232 | Rect checkboxRect1(margin, y, 18, 18); 233 | WidgetState checkboxState1(checkboxRect1); 234 | checkboxState1.hovered = (data->hoveredWidget == 0); 235 | checkboxState1.pressed = (data->pressedWidget == 0); 236 | 237 | data->renderer->drawModernCheckbox(checkboxState1, theme, data->tempOptions.darkMode); 238 | data->renderer->drawText(Translations::T("Dark Mode").c_str(), margin + 28, y + 2, theme.textColor); 239 | y += controlHeight + spacing; 240 | 241 | Rect checkboxRect2(margin, y, 18, 18); 242 | WidgetState checkboxState2(checkboxRect2); 243 | checkboxState2.hovered = (data->hoveredWidget == 1); 244 | checkboxState2.pressed = (data->pressedWidget == 1); 245 | 246 | data->renderer->drawModernCheckbox(checkboxState2, theme, data->tempOptions.autoReload); 247 | data->renderer->drawText(Translations::T("Auto-reload modified file").c_str(), margin + 28, y + 2, theme.textColor); 248 | y += controlHeight + spacing; 249 | 250 | if (!g_isNative) { 251 | Rect checkboxRect3(margin, y, 18, 18); 252 | WidgetState checkboxState3(checkboxRect3); 253 | checkboxState3.hovered = (data->hoveredWidget == 2); 254 | checkboxState3.pressed = (data->pressedWidget == 2); 255 | 256 | data->renderer->drawModernCheckbox(checkboxState3, theme, data->tempOptions.contextMenu); 257 | data->renderer->drawText(Translations::T("Add to context menu (right-click files)").c_str(), margin + 28, y + 2, theme.textColor); 258 | y += controlHeight + spacing * 2; 259 | } 260 | 261 | data->renderer->drawText(Translations::T("Language:").c_str(), margin, y, theme.textColor); 262 | y += controlHeight; 263 | 264 | Rect dropdownRect(margin + 20, y, 200, controlHeight); 265 | WidgetState dropdownState(dropdownRect); 266 | dropdownState.hovered = (data->hoveredWidget == 7); 267 | dropdownState.pressed = (data->pressedWidget == 7); 268 | 269 | data->renderer->drawDropdown( 270 | dropdownState, 271 | theme, 272 | data->languages[data->selectedLanguage], 273 | data->dropdownOpen, 274 | data->languages, 275 | data->selectedLanguage, 276 | data->hoveredDropdownItem, 277 | data->dropdownScrollOffset 278 | ); 279 | 280 | y += controlHeight + spacing * 2; 281 | 282 | y += spacing * 8; // Much more space! 283 | 284 | data->renderer->drawText(Translations::T("Default bytes per line:").c_str(), margin, y, theme.textColor); 285 | y += controlHeight; 286 | 287 | Rect radioRect1(margin + 20, y, 16, 16); 288 | WidgetState radioState1(radioRect1); 289 | radioState1.hovered = (data->hoveredWidget == 3); 290 | radioState1.pressed = (data->pressedWidget == 3); 291 | 292 | data->renderer->drawModernRadioButton(radioState1, theme, data->tempOptions.defaultBytesPerLine == 8); 293 | data->renderer->drawText(Translations::T("8 bytes").c_str(), margin + 45, y + 1, theme.textColor); 294 | y += controlHeight; 295 | 296 | Rect radioRect2(margin + 20, y, 16, 16); 297 | WidgetState radioState2(radioRect2); 298 | radioState2.hovered = (data->hoveredWidget == 4); 299 | radioState2.pressed = (data->pressedWidget == 4); 300 | 301 | data->renderer->drawModernRadioButton(radioState2, theme, data->tempOptions.defaultBytesPerLine == 16); 302 | data->renderer->drawText(Translations::T("16 bytes").c_str(), margin + 45, y + 1, theme.textColor); 303 | y += controlHeight + spacing * 2; 304 | 305 | int buttonWidth = 75; 306 | int buttonHeight = 25; 307 | int buttonY = windowHeight - margin - buttonHeight - 5; 308 | int buttonSpacing = 8; 309 | 310 | Rect okButtonRect(windowWidth - margin - buttonWidth * 2 - buttonSpacing, buttonY, buttonWidth, buttonHeight); 311 | WidgetState okButtonState(okButtonRect); 312 | okButtonState.hovered = (data->hoveredWidget == 5); 313 | okButtonState.pressed = (data->pressedWidget == 5); 314 | 315 | data->renderer->drawModernButton(okButtonState, theme, Translations::T("OK").c_str()); 316 | 317 | Rect cancelButtonRect(windowWidth - margin - buttonWidth, buttonY, buttonWidth, buttonHeight); 318 | WidgetState cancelButtonState(cancelButtonRect); 319 | cancelButtonState.hovered = (data->hoveredWidget == 6); 320 | cancelButtonState.pressed = (data->pressedWidget == 6); 321 | 322 | data->renderer->drawModernButton(cancelButtonState, theme, Translations::T("Cancel").c_str()); 323 | 324 | data->renderer->endFrame(); 325 | } 326 | 327 | void UpdateHoverState(OptionsDialogData* data, int x, int y, int windowWidth, int windowHeight) { 328 | int margin = 20; 329 | int startY = margin + 35; 330 | int controlHeight = 25; 331 | int spacing = 10; 332 | 333 | data->hoveredWidget = -1; 334 | data->hoveredDropdownItem = -1; 335 | 336 | Rect checkboxRect1(margin, startY, 18, 18); 337 | if (IsPointInRect(x, y, checkboxRect1)) { 338 | data->hoveredWidget = 0; 339 | return; 340 | } 341 | startY += controlHeight + spacing; 342 | 343 | Rect checkboxRect2(margin, startY, 18, 18); 344 | if (IsPointInRect(x, y, checkboxRect2)) { 345 | data->hoveredWidget = 1; 346 | return; 347 | } 348 | startY += controlHeight + spacing; 349 | 350 | if (!g_isNative) { 351 | Rect checkboxRect3(margin, startY, 18, 18); 352 | if (IsPointInRect(x, y, checkboxRect3)) { 353 | data->hoveredWidget = 2; 354 | return; 355 | } 356 | startY += controlHeight + spacing * 2; 357 | } 358 | 359 | startY += controlHeight; 360 | 361 | Rect dropdownRect(margin + 20, startY, 200, controlHeight); 362 | if (IsPointInRect(x, y, dropdownRect)) { 363 | data->hoveredWidget = 7; 364 | if (!data->dropdownOpen) { 365 | return; 366 | } 367 | } 368 | 369 | if (data->dropdownOpen) { 370 | int itemHeight = 28; 371 | int maxVisibleItems = 3; 372 | int dropdownItemY = startY + controlHeight + 2; 373 | 374 | for (size_t i = 0; i < data->languages.size(); i++) { 375 | int visualIndex = (int)i - data->dropdownScrollOffset; 376 | 377 | if (visualIndex < 0 || visualIndex >= maxVisibleItems) { 378 | continue; 379 | } 380 | 381 | Rect itemRect(margin + 20, dropdownItemY + (visualIndex * itemHeight), 200, itemHeight); 382 | if (IsPointInRect(x, y, itemRect)) { 383 | data->hoveredDropdownItem = i; 384 | return; 385 | } 386 | } 387 | } 388 | 389 | startY += controlHeight + spacing * 2 + controlHeight; 390 | 391 | Rect radioRect1(margin + 20, startY, 16, 16); 392 | if (IsPointInRect(x, y, radioRect1)) { 393 | data->hoveredWidget = 3; 394 | return; 395 | } 396 | startY += controlHeight; 397 | 398 | Rect radioRect2(margin + 20, startY, 16, 16); 399 | if (IsPointInRect(x, y, radioRect2)) { 400 | data->hoveredWidget = 4; 401 | return; 402 | } 403 | 404 | int buttonWidth = 75; 405 | int buttonHeight = 25; 406 | int buttonY = windowHeight - margin - buttonHeight - 5; 407 | int buttonSpacing = 8; 408 | 409 | Rect okButtonRect(windowWidth - margin - buttonWidth * 2 - buttonSpacing, buttonY, buttonWidth, buttonHeight); 410 | if (IsPointInRect(x, y, okButtonRect)) { 411 | data->hoveredWidget = 5; 412 | return; 413 | } 414 | 415 | Rect cancelButtonRect(windowWidth - margin - buttonWidth, buttonY, buttonWidth, buttonHeight); 416 | if (IsPointInRect(x, y, cancelButtonRect)) { 417 | data->hoveredWidget = 6; 418 | return; 419 | } 420 | } 421 | 422 | void HandleMouseClick(OptionsDialogData* data, int x, int y, int windowWidth, int windowHeight) { 423 | if (data->dropdownOpen && data->hoveredDropdownItem >= 0) { 424 | data->selectedLanguage = data->hoveredDropdownItem; 425 | data->tempOptions.language = data->languages[data->selectedLanguage]; 426 | Translations::SetLanguage(data->tempOptions.language); 427 | data->dropdownOpen = false; 428 | return; 429 | } 430 | 431 | if (data->hoveredWidget == -1) return; 432 | 433 | switch (data->hoveredWidget) { 434 | case 0: // Dark Mode checkbox 435 | data->tempOptions.darkMode = !data->tempOptions.darkMode; 436 | break; 437 | 438 | case 1: // Auto Reload checkbox 439 | data->tempOptions.autoReload = !data->tempOptions.autoReload; 440 | break; 441 | 442 | case 2: // Context Menu checkbox 443 | if (!g_isNative) { 444 | data->tempOptions.contextMenu = !data->tempOptions.contextMenu; 445 | } 446 | break; 447 | 448 | case 3: // 8 bytes radio 449 | data->tempOptions.defaultBytesPerLine = 8; 450 | break; 451 | 452 | case 4: // 16 bytes radio 453 | data->tempOptions.defaultBytesPerLine = 16; 454 | break; 455 | 456 | case 5: { // OK button 457 | #ifdef _WIN32 458 | bool wasRegistered = ContextMenuRegistry::IsRegistered(UserRole::CurrentUser); 459 | bool shouldBeRegistered = data->tempOptions.contextMenu; 460 | 461 | if (shouldBeRegistered && !wasRegistered) { 462 | wchar_t exePath[MAX_PATH]; 463 | GetModuleFileNameW(nullptr, exePath, MAX_PATH); 464 | 465 | for (int i = 0; exePath[i] != L'\0'; i++) { 466 | if (exePath[i] == L'/') { 467 | exePath[i] = L'\\'; 468 | } 469 | } 470 | 471 | if (!ContextMenuRegistry::Register(exePath, UserRole::CurrentUser)) { 472 | MessageBoxW(nullptr, 473 | L"Failed to register context menu.", 474 | L"Error", MB_OK | MB_ICONERROR); 475 | data->tempOptions.contextMenu = false; 476 | } 477 | } 478 | else if (!shouldBeRegistered && wasRegistered) { 479 | if (!ContextMenuRegistry::Unregister(UserRole::CurrentUser)) { 480 | MessageBoxW(nullptr, 481 | L"Failed to unregister context menu.", 482 | L"Error", MB_OK | MB_ICONERROR); 483 | data->tempOptions.contextMenu = true; 484 | } 485 | } 486 | #endif 487 | data->tempOptions.language = data->languages[data->selectedLanguage]; 488 | *data->originalOptions = data->tempOptions; 489 | data->dialogResult = true; 490 | data->running = false; 491 | break; 492 | } 493 | 494 | case 6: // Cancel button 495 | data->dialogResult = false; 496 | data->running = false; 497 | break; 498 | 499 | case 7: // Dropdown 500 | data->dropdownOpen = !data->dropdownOpen; 501 | if (!data->dropdownOpen) { 502 | data->dropdownScrollOffset = 0; 503 | } 504 | break; 505 | } 506 | } 507 | 508 | #ifdef _WIN32 509 | 510 | LRESULT CALLBACK OptionsWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 511 | OptionsDialogData* data = g_dialogData; 512 | 513 | switch (msg) { 514 | case WM_PAINT: { 515 | PAINTSTRUCT ps; 516 | BeginPaint(hwnd, &ps); 517 | if (data && data->renderer) { 518 | RECT rect; 519 | GetClientRect(hwnd, &rect); 520 | RenderOptionsDialog(data, rect.right, rect.bottom); 521 | } 522 | EndPaint(hwnd, &ps); 523 | return 0; 524 | } 525 | 526 | case WM_MOUSEMOVE: { 527 | if (data) { 528 | RECT rect; 529 | GetClientRect(hwnd, &rect); 530 | int x = LOWORD(lParam); 531 | int y = HIWORD(lParam); 532 | data->mouseX = x; 533 | data->mouseY = y; 534 | UpdateHoverState(data, x, y, rect.right, rect.bottom); 535 | InvalidateRect(hwnd, NULL, FALSE); 536 | } 537 | return 0; 538 | } 539 | case WM_MOUSEWHEEL: { 540 | if (data && data->dropdownOpen) { 541 | RECT rect; 542 | GetClientRect(hwnd, &rect); 543 | 544 | int margin = 20; 545 | int startY = margin + 35 + 25 + 10 + 25 + 10; // Calculate dropdown Y position 546 | if (!g_isNative) { 547 | startY += 25 + 10 * 2; 548 | } 549 | startY += 25; // Skip "Language:" label 550 | 551 | Rect dropdownRect(margin + 20, startY, 200, 25); 552 | 553 | if (data->mouseX >= dropdownRect.x && 554 | data->mouseX <= dropdownRect.x + dropdownRect.width && 555 | data->mouseY >= dropdownRect.y) { 556 | 557 | int delta = GET_WHEEL_DELTA_WPARAM(wParam); 558 | int maxVisibleItems = 3; 559 | int maxScroll = std::clamp((int)data->languages.size() - maxVisibleItems, 0, INT_MAX); 560 | 561 | if (delta > 0) { 562 | data->dropdownScrollOffset = std::clamp(data->dropdownScrollOffset - 1, 0, maxScroll); 563 | } 564 | else { 565 | data->dropdownScrollOffset = std::clamp(data->dropdownScrollOffset + 1, 0, maxScroll); 566 | } 567 | 568 | UpdateHoverState(data, data->mouseX, data->mouseY, rect.right, rect.bottom); 569 | InvalidateRect(hwnd, NULL, FALSE); 570 | return 0; 571 | } 572 | } 573 | break; 574 | } 575 | 576 | case WM_LBUTTONDOWN: { 577 | if (data) { 578 | RECT rect; 579 | GetClientRect(hwnd, &rect); 580 | int x = LOWORD(lParam); 581 | int y = HIWORD(lParam); 582 | data->mouseDown = true; 583 | data->pressedWidget = data->hoveredWidget; 584 | InvalidateRect(hwnd, NULL, FALSE); 585 | } 586 | return 0; 587 | } 588 | 589 | case WM_LBUTTONUP: { 590 | if (data) { 591 | RECT rect; 592 | GetClientRect(hwnd, &rect); 593 | int x = LOWORD(lParam); 594 | int y = HIWORD(lParam); 595 | data->mouseDown = false; 596 | 597 | if (data->pressedWidget == data->hoveredWidget || data->hoveredDropdownItem >= 0) { 598 | HandleMouseClick(data, x, y, rect.right, rect.bottom); 599 | } 600 | 601 | data->pressedWidget = -1; 602 | InvalidateRect(hwnd, NULL, FALSE); 603 | } 604 | return 0; 605 | } 606 | 607 | case WM_CLOSE: 608 | if (data) { 609 | data->dialogResult = false; 610 | data->running = false; 611 | } 612 | return 0; 613 | 614 | case WM_SIZE: { 615 | if (data && data->renderer) { 616 | RECT rect; 617 | GetClientRect(hwnd, &rect); 618 | if (rect.right > 0 && rect.bottom > 0 && rect.right < 8192 && rect.bottom < 8192) { 619 | data->renderer->resize(rect.right, rect.bottom); 620 | InvalidateRect(hwnd, NULL, FALSE); 621 | } 622 | } 623 | return 0; 624 | } 625 | } 626 | 627 | return DefWindowProc(hwnd, msg, wParam, lParam); 628 | } 629 | 630 | bool OptionsDialog::Show(HWND parent, AppOptions& options) 631 | { 632 | const wchar_t* className = L"CustomOptionsWindow"; 633 | 634 | WNDCLASSEXW wc = {}; 635 | wc.cbSize = sizeof(WNDCLASSEXW); 636 | wc.lpfnWndProc = OptionsWindowProc; 637 | wc.hInstance = GetModuleHandleW(NULL); 638 | wc.lpszClassName = className; 639 | wc.hCursor = LoadCursor(nullptr, IDC_ARROW); 640 | wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 641 | 642 | UnregisterClassW(className, wc.hInstance); 643 | if (!RegisterClassExW(&wc)) { 644 | return false; 645 | } 646 | 647 | OptionsDialogData data = {}; 648 | data.tempOptions = options; 649 | 650 | if (!g_isNative) { 651 | data.tempOptions.contextMenu = ContextMenuRegistry::IsRegistered(UserRole::CurrentUser); 652 | } 653 | else { 654 | data.tempOptions.contextMenu = false; 655 | } 656 | 657 | data.originalOptions = &options; 658 | data.dialogResult = false; 659 | data.running = true; 660 | g_dialogData = &data; 661 | 662 | for (size_t i = 0; i < data.languages.size(); i++) { 663 | if (data.languages[i] == options.language) { 664 | data.selectedLanguage = i; 665 | break; 666 | } 667 | } 668 | int width = 400; 669 | int height = 480; // Increased height for dropdown with full list 670 | 671 | RECT parentRect; 672 | GetWindowRect(parent, &parentRect); 673 | 674 | int x = parentRect.left + (parentRect.right - parentRect.left - width) / 2; 675 | int y = parentRect.top + (parentRect.bottom - parentRect.top - height) / 2; 676 | 677 | HWND hwnd = CreateWindowExW( 678 | WS_EX_DLGMODALFRAME | WS_EX_TOPMOST, 679 | className, 680 | L"Options", 681 | WS_POPUP | WS_CAPTION | WS_SYSMENU, 682 | x, y, width, height, 683 | parent, 684 | nullptr, 685 | GetModuleHandleW(NULL), 686 | nullptr 687 | ); 688 | 689 | if (!hwnd) { 690 | g_dialogData = nullptr; 691 | return false; 692 | } 693 | 694 | data.window = hwnd; 695 | 696 | if (data.tempOptions.darkMode) { 697 | BOOL dark = TRUE; 698 | constexpr DWORD DWMWA_USE_IMMERSIVE_DARK_MODE = 20; 699 | DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark)); 700 | } 701 | 702 | data.renderer = new RenderManager(); 703 | if (!data.renderer) { 704 | DestroyWindow(hwnd); 705 | g_dialogData = nullptr; 706 | return false; 707 | } 708 | 709 | if (!data.renderer->initialize(hwnd)) { 710 | delete data.renderer; 711 | DestroyWindow(hwnd); 712 | g_dialogData = nullptr; 713 | return false; 714 | } 715 | 716 | RECT clientRect; 717 | GetClientRect(hwnd, &clientRect); 718 | 719 | if (clientRect.right > 0 && clientRect.bottom > 0) { 720 | data.renderer->resize(clientRect.right, clientRect.bottom); 721 | } 722 | 723 | EnableWindow(parent, FALSE); 724 | 725 | ShowWindow(hwnd, SW_SHOW); 726 | UpdateWindow(hwnd); 727 | 728 | MSG msg; 729 | while (data.running && GetMessage(&msg, NULL, 0, 0)) { 730 | TranslateMessage(&msg); 731 | DispatchMessage(&msg); 732 | } 733 | 734 | if (data.renderer) { 735 | delete data.renderer; 736 | data.renderer = nullptr; 737 | } 738 | 739 | EnableWindow(parent, TRUE); 740 | SetForegroundWindow(parent); 741 | DestroyWindow(hwnd); 742 | UnregisterClassW(className, GetModuleHandleW(NULL)); 743 | 744 | g_dialogData = nullptr; 745 | 746 | return data.dialogResult; 747 | } 748 | 749 | #elif defined(__linux__) 750 | 751 | void ProcessOptionsEvent(OptionsDialogData* data, XEvent* event, int width, int height) { 752 | switch (event->type) { 753 | case Expose: 754 | RenderOptionsDialog(data, width, height); 755 | break; 756 | 757 | case MotionNotify: 758 | data->mouseX = event->xmotion.x; 759 | data->mouseY = event->xmotion.y; 760 | UpdateHoverState(data, event->xmotion.x, event->xmotion.y, width, height); 761 | RenderOptionsDialog(data, width, height); 762 | break; 763 | 764 | case ButtonPress: 765 | if (event->xbutton.button == Button1) { 766 | data->mouseDown = true; 767 | data->pressedWidget = data->hoveredWidget; 768 | RenderOptionsDialog(data, width, height); 769 | } 770 | else if (event->xbutton.button == Button4) { // Scroll up 771 | if (data->dropdownOpen) { 772 | int maxVisibleItems = 3; 773 | int maxScroll = std::clamp((int)data->languages.size() - maxVisibleItems, 0, INT_MAX); 774 | data->dropdownScrollOffset = std::clamp(data->dropdownScrollOffset - 1, 0, maxScroll); 775 | UpdateHoverState(data, data->mouseX, data->mouseY, width, height); 776 | RenderOptionsDialog(data, width, height); 777 | } 778 | } 779 | else if (event->xbutton.button == Button5) { // Scroll down 780 | if (data->dropdownOpen) { 781 | int maxVisibleItems = 3; 782 | int maxScroll = std::clamp((int)data->languages.size() - maxVisibleItems, 0, INT_MAX); 783 | data->dropdownScrollOffset = std::clamp(data->dropdownScrollOffset + 1, 0, maxScroll); 784 | UpdateHoverState(data, data->mouseX, data->mouseY, width, height); 785 | RenderOptionsDialog(data, width, height); 786 | } 787 | } 788 | break; 789 | 790 | case ButtonRelease: 791 | if (event->xbutton.button == Button1) { 792 | data->mouseDown = false; 793 | 794 | if (data->pressedWidget == data->hoveredWidget || data->hoveredDropdownItem >= 0) { 795 | HandleMouseClick(data, event->xbutton.x, event->xbutton.y, width, height); 796 | } 797 | 798 | data->pressedWidget = -1; 799 | RenderOptionsDialog(data, width, height); 800 | } 801 | break; 802 | 803 | case ClientMessage: 804 | if ((Atom)event->xclient.data.l[0] == data->wmDeleteWindow) { 805 | data->dialogResult = false; 806 | data->running = false; 807 | } 808 | break; 809 | 810 | case ConfigureNotify: 811 | if (event->xconfigure.width != width || event->xconfigure.height != height) { 812 | if (data->renderer) { 813 | data->renderer->resize(event->xconfigure.width, event->xconfigure.height); 814 | RenderOptionsDialog(data, event->xconfigure.width, event->xconfigure.height); 815 | } 816 | } 817 | break; 818 | } 819 | } 820 | 821 | bool OptionsDialog::Show(NativeWindow parent, AppOptions& options) { 822 | Display* display = XOpenDisplay(nullptr); 823 | if (!display) { 824 | return false; 825 | } 826 | 827 | int screen = DefaultScreen(display); 828 | Window rootWindow = RootWindow(display, screen); 829 | 830 | OptionsDialogData data = {}; 831 | data.tempOptions = options; 832 | 833 | if (!g_isNative) { 834 | data.tempOptions.contextMenu = false; 835 | } 836 | else { 837 | data.tempOptions.contextMenu = false; 838 | } 839 | 840 | data.originalOptions = &options; 841 | data.dialogResult = false; 842 | data.running = true; 843 | data.display = display; 844 | g_dialogData = &data; 845 | 846 | for (size_t i = 0; i < data.languages.size(); i++) { 847 | if (data.languages[i] == options.language) { 848 | data.selectedLanguage = i; 849 | break; 850 | } 851 | } 852 | 853 | int width = 400; 854 | int height = 480; 855 | 856 | Window window = XCreateSimpleWindow( 857 | display, rootWindow, 858 | 100, 100, width, height, 1, 859 | BlackPixel(display, screen), 860 | WhitePixel(display, screen) 861 | ); 862 | 863 | data.window = window; 864 | 865 | XStoreName(display, window, "Options"); 866 | 867 | Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); 868 | Atom wmWindowTypeDialog = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); 869 | XChangeProperty(display, window, wmWindowType, XA_ATOM, 32, PropModeReplace, 870 | (unsigned char*)&wmWindowTypeDialog, 1); 871 | 872 | Atom wmDelete = XInternAtom(display, "WM_DELETE_WINDOW", True); 873 | XSetWMProtocols(display, window, &wmDelete, 1); 874 | data.wmDeleteWindow = wmDelete; 875 | 876 | XSelectInput(display, window, 877 | ExposureMask | ButtonPressMask | ButtonReleaseMask | 878 | PointerMotionMask | StructureNotifyMask); 879 | 880 | XMapWindow(display, window); 881 | XFlush(display); 882 | 883 | data.renderer = new RenderManager(); 884 | if (!data.renderer || !data.renderer->initialize(window)) { 885 | if (data.renderer) delete data.renderer; 886 | XDestroyWindow(display, window); 887 | XCloseDisplay(display); 888 | g_dialogData = nullptr; 889 | return false; 890 | } 891 | 892 | data.renderer->resize(width, height); 893 | 894 | XEvent event; 895 | while (data.running) { 896 | XNextEvent(display, &event); 897 | ProcessOptionsEvent(&data, &event, width, height); 898 | } 899 | 900 | if (data.renderer) { 901 | delete data.renderer; 902 | } 903 | 904 | XDestroyWindow(display, window); 905 | XCloseDisplay(display); 906 | g_dialogData = nullptr; 907 | 908 | return data.dialogResult; 909 | } 910 | 911 | #elif defined(__APPLE__) 912 | 913 | bool OptionsDialog::Show(NativeWindow parent, AppOptions& options) { 914 | (void)parent; 915 | (void)options; 916 | return false; 917 | } 918 | 919 | #endif 920 | -------------------------------------------------------------------------------- /HexViewer/src/core/render.cpp: -------------------------------------------------------------------------------- 1 | #include "render.h" 2 | #include 3 | #include 4 | 5 | #ifdef _WIN32 6 | #define NATIVE_WINDOW_NULL nullptr 7 | #elif __APPLE__ 8 | #define NATIVE_WINDOW_NULL nullptr 9 | #else 10 | #define NATIVE_WINDOW_NULL 0UL 11 | #endif 12 | 13 | RenderManager::RenderManager() 14 | : window(NATIVE_WINDOW_NULL), 15 | windowWidth(0), windowHeight(0) 16 | #ifdef _WIN32 17 | , hdc(nullptr), memDC(nullptr), memBitmap(nullptr), oldBitmap(nullptr), 18 | font(nullptr), pixels(nullptr) 19 | #elif __APPLE__ 20 | , context(nullptr), backBuffer(nullptr) 21 | #else 22 | , display(nullptr), gc(nullptr), backBuffer(0), fontInfo(nullptr) 23 | #endif 24 | { 25 | currentTheme = Theme::Dark(); 26 | } 27 | 28 | 29 | RenderManager::~RenderManager() { 30 | cleanup(); 31 | } 32 | 33 | bool RenderManager::initialize(NativeWindow win) { 34 | window = win; 35 | 36 | #ifdef _WIN32 37 | hdc = GetDC((HWND)window); 38 | if (!hdc) return false; 39 | 40 | memDC = CreateCompatibleDC(hdc); 41 | if (!memDC) { 42 | ReleaseDC((HWND)window, hdc); 43 | return false; 44 | } 45 | 46 | createFont(); 47 | return true; 48 | 49 | #elif __APPLE__ 50 | return false; 51 | 52 | #else 53 | display = XOpenDisplay(nullptr); 54 | if (!display) return false; 55 | 56 | gc = XCreateGC(display, window, 0, nullptr); 57 | if (!gc) { 58 | XCloseDisplay(display); 59 | return false; 60 | } 61 | 62 | fontInfo = XLoadQueryFont(display, "fixed"); 63 | if (!fontInfo) { 64 | fontInfo = XLoadQueryFont(display, "*"); 65 | } 66 | if (fontInfo) { 67 | XSetFont(display, gc, fontInfo->fid); 68 | } 69 | 70 | return true; 71 | #endif 72 | } 73 | 74 | void RenderManager::cleanup() { 75 | #ifdef _WIN32 76 | destroyFont(); 77 | 78 | if (memBitmap) { 79 | SelectObject(memDC, oldBitmap); 80 | DeleteObject(memBitmap); 81 | memBitmap = nullptr; 82 | } 83 | 84 | if (memDC) { 85 | DeleteDC(memDC); 86 | memDC = nullptr; 87 | } 88 | 89 | if (hdc) { 90 | ReleaseDC((HWND)window, hdc); 91 | hdc = nullptr; 92 | } 93 | 94 | #elif __APPLE__ 95 | if (backBuffer) { 96 | free(backBuffer); 97 | backBuffer = nullptr; 98 | } 99 | 100 | #else 101 | if (backBuffer) { 102 | XFreePixmap(display, backBuffer); 103 | backBuffer = 0; 104 | } 105 | 106 | if (fontInfo) { 107 | XFreeFont(display, fontInfo); 108 | fontInfo = nullptr; 109 | } 110 | 111 | if (gc) { 112 | XFreeGC(display, gc); 113 | gc = nullptr; 114 | } 115 | 116 | if (display) { 117 | XCloseDisplay(display); 118 | display = nullptr; 119 | } 120 | #endif 121 | } 122 | 123 | void RenderManager::resize(int width, int height) { 124 | #ifdef _WIN32 125 | if (!memDC) return; // Not initialized yet 126 | #elif __APPLE__ 127 | if (!context) return; // Not initialized yet 128 | #else 129 | if (!display || !window) return; // Not initialized yet 130 | #endif 131 | 132 | if (width <= 0 || height <= 0) return; 133 | if (width > 8192 || height > 8192) return; 134 | 135 | windowWidth = width; 136 | windowHeight = height; 137 | #ifdef _WIN32 138 | if (memBitmap) { 139 | SelectObject(memDC, oldBitmap); 140 | DeleteObject(memBitmap); 141 | } 142 | 143 | memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); 144 | bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 145 | bitmapInfo.bmiHeader.biWidth = width; 146 | bitmapInfo.bmiHeader.biHeight = -height; // Top-down 147 | bitmapInfo.bmiHeader.biPlanes = 1; 148 | bitmapInfo.bmiHeader.biBitCount = 32; 149 | bitmapInfo.bmiHeader.biCompression = BI_RGB; 150 | 151 | memBitmap = CreateDIBSection(memDC, &bitmapInfo, DIB_RGB_COLORS, &pixels, nullptr, 0); 152 | oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap); 153 | 154 | #elif __APPLE__ 155 | if (backBuffer) { 156 | free(backBuffer); 157 | } 158 | backBuffer = malloc(width * height * 4); 159 | 160 | #else 161 | if (backBuffer) { 162 | XFreePixmap(display, backBuffer); 163 | } 164 | backBuffer = XCreatePixmap(display, window, width, height, 165 | DefaultDepth(display, DefaultScreen(display))); 166 | #endif 167 | } 168 | 169 | void RenderManager::createFont() { 170 | #ifdef _WIN32 171 | font = CreateFontA( 172 | 16, // Height 173 | 0, // Width (0 = default) 174 | 0, // Escapement 175 | 0, // Orientation 176 | FW_NORMAL, // Weight 177 | FALSE, // Italic 178 | FALSE, // Underline 179 | FALSE, // StrikeOut 180 | DEFAULT_CHARSET, // CharSet 181 | OUT_DEFAULT_PRECIS, // OutPrecision 182 | CLIP_DEFAULT_PRECIS, // ClipPrecision 183 | DEFAULT_QUALITY, // Quality 184 | FIXED_PITCH | FF_MODERN, // PitchAndFamily - IMPORTANT: FIXED_PITCH! 185 | "Consolas" // FaceName 186 | ); 187 | 188 | if (font && memDC) { 189 | SelectObject(memDC, font); 190 | 191 | SIZE textSize; 192 | GetTextExtentPoint32A(memDC, "0", 1, &textSize); 193 | 194 | char debugMsg[128]; 195 | snprintf(debugMsg, sizeof(debugMsg), 196 | "Font created: charWidth=%d, charHeight=%d\n", 197 | textSize.cx, textSize.cy); 198 | OutputDebugStringA(debugMsg); 199 | } 200 | #endif 201 | } 202 | 203 | void RenderManager::destroyFont() { 204 | #ifdef _WIN32 205 | if (font) { 206 | DeleteObject(font); 207 | font = nullptr; 208 | } 209 | #endif 210 | } 211 | 212 | void RenderManager::beginFrame() { 213 | } 214 | 215 | void RenderManager::endFrame() { 216 | #ifdef _WIN32 217 | BitBlt(hdc, 0, 0, windowWidth, windowHeight, memDC, 0, 0, SRCCOPY); 218 | 219 | #elif __APPLE__ 220 | 221 | #else 222 | XCopyArea(display, backBuffer, window, gc, 0, 0, 223 | windowWidth, windowHeight, 0, 0); 224 | XFlush(display); 225 | #endif 226 | } 227 | 228 | void RenderManager::clear(const Color& color) { 229 | #ifdef _WIN32 230 | RECT rect = { 0, 0, windowWidth, windowHeight }; 231 | HBRUSH brush = CreateSolidBrush(RGB(color.r, color.g, color.b)); 232 | FillRect(memDC, &rect, brush); 233 | DeleteObject(brush); 234 | 235 | #elif __APPLE__ 236 | if (backBuffer) { 237 | uint32_t* buf = (uint32_t*)backBuffer; 238 | uint32_t colorValue = (color.r << 16) | (color.g << 8) | color.b; 239 | for (int i = 0; i < windowWidth * windowHeight; i++) { 240 | buf[i] = colorValue; 241 | } 242 | } 243 | 244 | #else 245 | XSetForeground(display, gc, 246 | (color.r << 16) | (color.g << 8) | color.b); 247 | XFillRectangle(display, backBuffer, gc, 0, 0, windowWidth, windowHeight); 248 | #endif 249 | } 250 | 251 | void RenderManager::setColor(const Color& color) { 252 | #ifdef _WIN32 253 | SetTextColor(memDC, RGB(color.r, color.g, color.b)); 254 | SetBkMode(memDC, TRANSPARENT); 255 | 256 | #elif __APPLE__ 257 | 258 | #else 259 | XSetForeground(display, gc, 260 | (color.r << 16) | (color.g << 8) | color.b); 261 | #endif 262 | } 263 | 264 | void RenderManager::drawRect(const Rect& rect, const Color& color, bool filled) { 265 | #ifdef _WIN32 266 | if (filled) { 267 | RECT r = { rect.x, rect.y, rect.x + rect.width, rect.y + rect.height }; 268 | HBRUSH brush = CreateSolidBrush(RGB(color.r, color.g, color.b)); 269 | FillRect(memDC, &r, brush); 270 | DeleteObject(brush); 271 | } 272 | else { 273 | HPEN pen = CreatePen(PS_SOLID, 1, RGB(color.r, color.g, color.b)); 274 | HPEN oldPen = (HPEN)SelectObject(memDC, pen); 275 | HBRUSH oldBrush = (HBRUSH)SelectObject(memDC, GetStockObject(NULL_BRUSH)); 276 | 277 | Rectangle(memDC, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height); 278 | 279 | SelectObject(memDC, oldBrush); 280 | SelectObject(memDC, oldPen); 281 | DeleteObject(pen); 282 | } 283 | 284 | #elif __APPLE__ 285 | 286 | #else 287 | XSetForeground(display, gc, 288 | (color.r << 16) | (color.g << 8) | color.b); 289 | if (filled) { 290 | XFillRectangle(display, backBuffer, gc, 291 | rect.x, rect.y, rect.width, rect.height); 292 | } 293 | else { 294 | XDrawRectangle(display, backBuffer, gc, 295 | rect.x, rect.y, rect.width, rect.height); 296 | } 297 | #endif 298 | } 299 | 300 | void RenderManager::drawLine(int x1, int y1, int x2, int y2, const Color& color) { 301 | #ifdef _WIN32 302 | HPEN pen = CreatePen(PS_SOLID, 1, RGB(color.r, color.g, color.b)); 303 | HPEN oldPen = (HPEN)SelectObject(memDC, pen); 304 | 305 | MoveToEx(memDC, x1, y1, nullptr); 306 | LineTo(memDC, x2, y2); 307 | 308 | SelectObject(memDC, oldPen); 309 | DeleteObject(pen); 310 | 311 | #elif __APPLE__ 312 | 313 | #else 314 | XSetForeground(display, gc, 315 | (color.r << 16) | (color.g << 8) | color.b); 316 | XDrawLine(display, backBuffer, gc, x1, y1, x2, y2); 317 | #endif 318 | } 319 | 320 | void RenderManager::drawText(const std::string& text, int x, int y, const Color& color) { 321 | #ifdef _WIN32 322 | setColor(color); 323 | TextOutA(memDC, x, y, text.c_str(), (int)text.length()); 324 | 325 | #elif __APPLE__ 326 | 327 | #else 328 | XSetForeground(display, gc, 329 | (color.r << 16) | (color.g << 8) | color.b); 330 | XDrawString(display, backBuffer, gc, x, y + 12, 331 | text.c_str(), text.length()); 332 | #endif 333 | } 334 | 335 | 336 | void RenderManager::drawRoundedRect(const Rect& rect, float radius, const Color& color, bool filled) { 337 | #ifdef _WIN32 338 | if (filled) { 339 | HRGN rgn = CreateRoundRectRgn( 340 | rect.x, rect.y, 341 | rect.x + rect.width, rect.y + rect.height, 342 | (int)radius * 2, (int)radius * 2 343 | ); 344 | 345 | HBRUSH brush = CreateSolidBrush(RGB(color.r, color.g, color.b)); 346 | FillRgn(memDC, rgn, brush); 347 | DeleteObject(brush); 348 | DeleteObject(rgn); 349 | } 350 | else { 351 | HPEN pen = CreatePen(PS_SOLID, 1, RGB(color.r, color.g, color.b)); 352 | HPEN oldPen = (HPEN)SelectObject(memDC, pen); 353 | HBRUSH oldBrush = (HBRUSH)SelectObject(memDC, GetStockObject(NULL_BRUSH)); 354 | 355 | RoundRect(memDC, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, 356 | (int)radius * 2, (int)radius * 2); 357 | 358 | SelectObject(memDC, oldBrush); 359 | SelectObject(memDC, oldPen); 360 | DeleteObject(pen); 361 | } 362 | #elif __APPLE__ 363 | #else 364 | drawRect(rect, color, filled); 365 | #endif 366 | } 367 | 368 | void RenderManager::drawModernButton(const WidgetState& state, const Theme& theme, const std::string& label) { 369 | float radius = 6.0f; 370 | 371 | Color fillColor = theme.buttonNormal; 372 | if (!state.enabled) { 373 | fillColor = theme.buttonDisabled; 374 | } 375 | else if (state.pressed) { 376 | fillColor = theme.buttonPressed; 377 | } 378 | else if (state.hovered) { 379 | fillColor = theme.buttonHover; 380 | } 381 | 382 | drawRoundedRect(state.rect, radius, fillColor, true); 383 | 384 | if (state.enabled) { 385 | Color borderColor = state.pressed ? 386 | Color(fillColor.r - 20, fillColor.g - 20, fillColor.b - 20) : 387 | Color(fillColor.r - 10, fillColor.g - 10, fillColor.b - 10); 388 | drawRoundedRect(state.rect, radius, borderColor, false); 389 | } 390 | 391 | int textWidth = label.length() * 8; 392 | int textHeight = 16; 393 | int textX = state.rect.x + (state.rect.width - textWidth) / 2; 394 | int textY = state.rect.y + (state.rect.height - textHeight) / 2; 395 | 396 | Color textColor = state.enabled ? theme.buttonText : 397 | Color(theme.buttonText.r / 2, theme.buttonText.g / 2, theme.buttonText.b / 2); 398 | drawText(label, textX, textY, textColor); 399 | } 400 | 401 | void RenderManager::drawModernCheckbox(const WidgetState& state, const Theme& theme, bool checked) { 402 | float radius = 3.0f; 403 | 404 | Color bgColor = theme.controlBackground; 405 | Color borderColor = theme.controlBorder; 406 | 407 | if (state.hovered) { 408 | borderColor = Color(borderColor.r + 30, borderColor.g + 30, borderColor.b + 30); 409 | } 410 | 411 | if (checked) { 412 | bgColor = theme.controlCheck; 413 | borderColor = theme.controlCheck; 414 | } 415 | 416 | drawRoundedRect(state.rect, radius, bgColor, true); 417 | 418 | if (!checked || state.hovered) { 419 | drawRoundedRect(state.rect, radius, borderColor, false); 420 | } 421 | 422 | if (checked) { 423 | Color checkColor = Color(255, 255, 255); 424 | int cx = state.rect.x + 4; 425 | int cy = state.rect.y + 9; 426 | 427 | drawLine(cx, cy, cx + 3, cy + 4, checkColor); 428 | drawLine(cx + 1, cy, cx + 4, cy + 4, checkColor); 429 | drawLine(cx + 3, cy + 4, cx + 10, cy - 4, checkColor); 430 | drawLine(cx + 4, cy + 4, cx + 11, cy - 4, checkColor); 431 | } 432 | } 433 | 434 | 435 | void RenderManager::drawModernRadioButton(const WidgetState& state, const Theme& theme, bool selected) { 436 | #ifdef _WIN32 437 | Color bgColor = theme.controlBackground; 438 | Color borderColor = theme.controlBorder; 439 | 440 | if (state.hovered) { 441 | borderColor = Color(borderColor.r + 30, borderColor.g + 30, borderColor.b + 30); 442 | } 443 | 444 | int centerX = state.rect.x + state.rect.width / 2; 445 | int centerY = state.rect.y + state.rect.height / 2; 446 | int outerRadius = state.rect.width / 2; 447 | 448 | HBRUSH bgBrush = CreateSolidBrush(RGB(bgColor.r, bgColor.g, bgColor.b)); 449 | HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(borderColor.r, borderColor.g, borderColor.b)); 450 | 451 | HBRUSH oldBrush = (HBRUSH)SelectObject(memDC, bgBrush); 452 | HPEN oldPen = (HPEN)SelectObject(memDC, borderPen); 453 | 454 | Ellipse(memDC, 455 | centerX - outerRadius, 456 | centerY - outerRadius, 457 | centerX + outerRadius, 458 | centerY + outerRadius); 459 | 460 | SelectObject(memDC, oldBrush); 461 | SelectObject(memDC, oldPen); 462 | DeleteObject(bgBrush); 463 | DeleteObject(borderPen); 464 | 465 | if (selected) { 466 | int innerRadius = outerRadius - 4; 467 | Color dotColor = theme.controlCheck; 468 | 469 | HBRUSH dotBrush = CreateSolidBrush(RGB(dotColor.r, dotColor.g, dotColor.b)); 470 | HPEN dotPen = CreatePen(PS_SOLID, 1, RGB(dotColor.r, dotColor.g, dotColor.b)); 471 | 472 | oldBrush = (HBRUSH)SelectObject(memDC, dotBrush); 473 | oldPen = (HPEN)SelectObject(memDC, dotPen); 474 | 475 | Ellipse(memDC, 476 | centerX - innerRadius, 477 | centerY - innerRadius, 478 | centerX + innerRadius, 479 | centerY + innerRadius); 480 | 481 | SelectObject(memDC, oldBrush); 482 | SelectObject(memDC, oldPen); 483 | DeleteObject(dotBrush); 484 | DeleteObject(dotPen); 485 | } 486 | 487 | #elif __APPLE__ 488 | #else 489 | Display* display = this->display; 490 | GC gc = this->gc; 491 | 492 | Color bgColor = theme.controlBackground; 493 | Color borderColor = theme.controlBorder; 494 | 495 | if (state.hovered) { 496 | borderColor = Color(borderColor.r + 30, borderColor.g + 30, borderColor.b + 30); 497 | } 498 | 499 | int centerX = state.rect.x + state.rect.width / 2; 500 | int centerY = state.rect.y + state.rect.height / 2; 501 | int outerRadius = state.rect.width / 2; 502 | 503 | XSetForeground(display, gc, (bgColor.r << 16) | (bgColor.g << 8) | bgColor.b); 504 | XFillArc(display, backBuffer, gc, 505 | state.rect.x, state.rect.y, state.rect.width, state.rect.height, 506 | 0, 360 * 64); 507 | 508 | XSetForeground(display, gc, (borderColor.r << 16) | (borderColor.g << 8) | borderColor.b); 509 | XDrawArc(display, backBuffer, gc, 510 | state.rect.x, state.rect.y, state.rect.width, state.rect.height, 511 | 0, 360 * 64); 512 | 513 | if (selected) { 514 | int innerRadius = outerRadius - 4; 515 | Color dotColor = theme.controlCheck; 516 | 517 | XSetForeground(display, gc, (dotColor.r << 16) | (dotColor.g << 8) | dotColor.b); 518 | XFillArc(display, backBuffer, gc, 519 | centerX - innerRadius, centerY - innerRadius, 520 | innerRadius * 2, innerRadius * 2, 521 | 0, 360 * 64); 522 | } 523 | #endif 524 | } 525 | 526 | void RenderManager::drawDropdown( 527 | const WidgetState& state, 528 | const Theme& theme, 529 | const std::string& selectedText, 530 | bool isOpen, 531 | const std::vector& items, 532 | int selectedIndex, 533 | int hoveredIndex, 534 | int scrollOffset) 535 | { 536 | float radius = 4.0f; 537 | 538 | Color bgColor = theme.controlBackground; 539 | bgColor.a = 255; 540 | 541 | drawRect(state.rect, bgColor, true); 542 | 543 | Color borderColor = theme.controlBorder; 544 | 545 | if (state.hovered && !isOpen) { 546 | borderColor = Color( 547 | std::clamp(borderColor.r + 30, 0, 255), 548 | std::clamp(borderColor.g + 30, 0, 255), 549 | std::clamp(borderColor.b + 30, 0, 255) 550 | ); 551 | } 552 | 553 | drawRect(state.rect, bgColor, true); 554 | drawRoundedRect(state.rect, radius, bgColor, true); 555 | drawRoundedRect(state.rect, radius, borderColor, false); 556 | 557 | int textX = state.rect.x + 10; 558 | int textY = state.rect.y + (state.rect.height / 2) - 6; 559 | drawText(selectedText, textX, textY, theme.textColor); 560 | 561 | int arrowX = state.rect.x + state.rect.width - 18; 562 | int arrowY = state.rect.y + (state.rect.height / 2); 563 | 564 | Color arrowColor = theme.textColor; 565 | 566 | if (isOpen) { 567 | drawLine(arrowX + 4, arrowY - 3, arrowX, arrowY + 2, arrowColor); 568 | drawLine(arrowX + 4, arrowY - 3, arrowX + 8, arrowY + 2, arrowColor); 569 | drawLine(arrowX, arrowY + 2, arrowX + 8, arrowY + 2, arrowColor); 570 | } 571 | else { 572 | drawLine(arrowX, arrowY - 2, arrowX + 8, arrowY - 2, arrowColor); 573 | drawLine(arrowX, arrowY - 2, arrowX + 4, arrowY + 3, arrowColor); 574 | drawLine(arrowX + 8, arrowY - 2, arrowX + 4, arrowY + 3, arrowColor); 575 | } 576 | 577 | if (isOpen && !items.empty()) { 578 | int itemHeight = 28; 579 | int maxVisibleItems = 3; 580 | 581 | int totalItems = (int)items.size(); 582 | 583 | int maxScroll = std::clamp(totalItems - maxVisibleItems, 0, INT_MAX); 584 | scrollOffset = std::clamp(scrollOffset, 0, maxScroll); 585 | int remaining = totalItems - scrollOffset; 586 | int visibleItems = std::clamp(remaining, 0, maxVisibleItems); 587 | 588 | int listHeight = itemHeight * visibleItems; 589 | int listY = state.rect.y + state.rect.height + 2; 590 | 591 | Rect clearRect(state.rect.x - 5, listY - 5, state.rect.width + 10, listHeight + 10); 592 | Color windowBg = theme.windowBackground; 593 | windowBg.a = 255; 594 | drawRect(clearRect, windowBg, true); 595 | 596 | Rect shadowRect(state.rect.x + 3, listY + 3, state.rect.width, listHeight); 597 | Color shadowColor(0, 0, 0, 80); 598 | drawRect(shadowRect, shadowColor, true); 599 | 600 | Rect listRect(state.rect.x, listY, state.rect.width, listHeight); 601 | 602 | Color listBg = theme.menuBackground; 603 | listBg.a = 255; 604 | 605 | drawRect(listRect, listBg, true); 606 | drawRoundedRect(listRect, radius, listBg, true); 607 | 608 | Color listBorder = theme.menuBorder; 609 | drawRoundedRect(listRect, radius, listBorder, false); 610 | 611 | for (int visualIndex = 0; visualIndex < visibleItems; visualIndex++) { 612 | size_t actualIndex = scrollOffset + visualIndex; 613 | 614 | Rect itemRect( 615 | state.rect.x + 1, 616 | listY + (visualIndex * itemHeight) + 1, 617 | state.rect.width - 2, 618 | itemHeight - 1 619 | ); 620 | 621 | bool isSelected = ((int)actualIndex == selectedIndex); 622 | bool isHovered = ((int)actualIndex == hoveredIndex); 623 | 624 | Color itemBg; 625 | 626 | if (isHovered) { 627 | itemBg = theme.menuHover; 628 | } 629 | else if (isSelected) { 630 | itemBg = Color( 631 | (theme.controlCheck.r + theme.menuBackground.r) / 2, 632 | (theme.controlCheck.g + theme.menuBackground.g) / 2, 633 | (theme.controlCheck.b + theme.menuBackground.b) / 2 634 | ); 635 | } 636 | else { 637 | itemBg = listBg; 638 | } 639 | 640 | itemBg.a = 255; 641 | 642 | drawRect(itemRect, itemBg, true); 643 | 644 | int itemTextX = itemRect.x + 10; 645 | int itemTextY = itemRect.y + (itemRect.height / 2) - 6; 646 | 647 | Color textColor = theme.textColor; 648 | if (isSelected) { 649 | textColor = Color( 650 | std::clamp(textColor.r + 20, 0, 255), 651 | std::clamp(textColor.g + 20, 0, 255), 652 | std::clamp(textColor.b + 20, 0, 255) 653 | ); 654 | } 655 | 656 | drawText(items[actualIndex], itemTextX, itemTextY, textColor); 657 | 658 | if (isSelected) { 659 | int checkX = itemRect.x + itemRect.width - 20; 660 | int checkY = itemRect.y + itemRect.height / 2; 661 | Color checkColor = theme.controlCheck; 662 | 663 | drawLine(checkX, checkY, checkX + 2, checkY + 3, checkColor); 664 | drawLine(checkX + 2, checkY + 3, checkX + 6, checkY - 2, checkColor); 665 | drawLine(checkX + 1, checkY, checkX + 3, checkY + 3, checkColor); 666 | drawLine(checkX + 3, checkY + 3, checkX + 7, checkY - 2, checkColor); 667 | } 668 | 669 | if (visualIndex < visibleItems - 1) { 670 | Color separatorColor = Color( 671 | theme.separator.r, 672 | theme.separator.g, 673 | theme.separator.b, 674 | 100 675 | ); 676 | drawLine( 677 | itemRect.x + 8, 678 | itemRect.y + itemRect.height, 679 | itemRect.x + itemRect.width - 8, 680 | itemRect.y + itemRect.height, 681 | separatorColor 682 | ); 683 | } 684 | } 685 | 686 | if (scrollOffset > 0) { 687 | int arrowTopX = state.rect.x + state.rect.width / 2 - 4; 688 | int arrowTopY = listY + 8; 689 | Color scrollHint = theme.textColor; 690 | drawText("^", arrowTopX, arrowTopY - 4, scrollHint); 691 | } 692 | 693 | if (scrollOffset < maxScroll) { 694 | int arrowBottomX = state.rect.x + state.rect.width / 2 - 4; 695 | int arrowBottomY = listY + listHeight - 12; 696 | Color scrollHint = theme.textColor; 697 | drawText("v", arrowBottomX, arrowBottomY, scrollHint); 698 | } 699 | } 700 | } 701 | 702 | PointF RenderManager::GetBytePointF(long long byteIndex) { 703 | Point gp = GetGridBytePoint(byteIndex); 704 | return GetBytePointF(gp); 705 | } 706 | 707 | PointF RenderManager::GetBytePointF(Point gp) { 708 | float x = (3.0f * _charWidth) * gp.X + _hexAreaX; 709 | float y = gp.Y * _charHeight + _hexAreaY; 710 | return PointF(x, y); 711 | } 712 | 713 | Point RenderManager::GetGridBytePoint(long long byteIndex) { 714 | int row = static_cast(floor(static_cast(byteIndex) / 715 | static_cast(_bytesPerLine))); 716 | int column = static_cast(byteIndex % _bytesPerLine); 717 | return Point(column, row); 718 | } 719 | 720 | BytePositionInfo RenderManager::GetHexBytePositionInfo(Point screenPoint) { 721 | int relX = screenPoint.X - _hexAreaX; 722 | int relY = screenPoint.Y - _hexAreaY; 723 | int charX = relX / _charWidth; 724 | int charY = relY / _charHeight; 725 | int column = charX / 3; 726 | int row = charY; 727 | 728 | long long bytePos = _startByte + (row * _bytesPerLine) + column; 729 | int byteCharPos = charX % 3; 730 | if (byteCharPos > 1) { 731 | byteCharPos = 1; 732 | } 733 | 734 | return BytePositionInfo(bytePos, byteCharPos); 735 | } 736 | 737 | void RenderManager::DrawCaret() { 738 | if (_bytePos < 0) return; 739 | extern bool caretVisible; 740 | if (!caretVisible) return; 741 | 742 | long long relativePos = _bytePos - _startByte; 743 | if (relativePos < 0 || relativePos >= (_bytesPerLine * _visibleLines)) { 744 | return; 745 | } 746 | 747 | PointF caretPos = GetBytePointF(relativePos); 748 | 749 | caretPos.X += _byteCharacterPos * _charWidth; 750 | int caretWidth = 2; 751 | int caretHeight = _charHeight; 752 | 753 | Rect caretRect( 754 | static_cast(caretPos.X), 755 | static_cast(caretPos.Y), 756 | caretWidth, 757 | caretHeight 758 | ); 759 | 760 | Color caretColor = currentTheme.textColor; 761 | caretColor.a = 255; 762 | 763 | drawRect(caretRect, caretColor, true); 764 | } 765 | 766 | long long RenderManager::ScreenToByteIndex(int mouseX, int mouseY) { 767 | BytePositionInfo info = GetHexBytePositionInfo(Point(mouseX, mouseY)); 768 | return info.Index; 769 | } 770 | 771 | #ifdef _WIN32 772 | void RenderManager::drawBitmap(void* hBitmap, int width, int height, int x, int y) { 773 | if (!memDC || !hBitmap) return; 774 | HDC tempDC = CreateCompatibleDC(memDC); 775 | HBITMAP oldBmp = (HBITMAP)SelectObject(tempDC, (HBITMAP)hBitmap); 776 | BitBlt(memDC, x, y, width, height, tempDC, 0, 0, SRCCOPY); 777 | SelectObject(tempDC, oldBmp); 778 | DeleteDC(tempDC); 779 | } 780 | #endif 781 | #ifdef __APPLE__ 782 | void RenderManager::drawImage(NSImage* image, int width, int height, int x, int y) { 783 | if (!backBuffer || !image) return; 784 | [image drawInRect : NSMakeRect(x, y, width, height) 785 | fromRect : NSZeroRect 786 | operation : NSCompositingOperationSourceOver 787 | fraction : 1.0] ; 788 | } 789 | #endif 790 | #ifdef __linux__ 791 | void RenderManager::drawX11Pixmap(Pixmap pixmap, int width, int height, int x, int y) { 792 | if (!display || !backBuffer || !pixmap) return; 793 | GC tempGC = XCreateGC(display, backBuffer, 0, nullptr); 794 | XCopyArea(display, pixmap, backBuffer, tempGC, 0, 0, width, height, x, y); 795 | XFreeGC(display, tempGC); 796 | } 797 | #endif 798 | 799 | 800 | void RenderManager::drawProgressBar(const Rect& rect, float progress, const Theme& theme) { 801 | progress = std::clamp(progress, 0.0f, 1.0f); 802 | 803 | float radius = 4.0f; 804 | 805 | Color bgColor(50, 50, 50); 806 | if (theme.windowBackground.r > 128) { // Light theme 807 | bgColor = Color(220, 220, 220); 808 | } 809 | drawRoundedRect(rect, radius, bgColor, true); 810 | 811 | int fillWidth = (int)(rect.width * progress); 812 | if (fillWidth > 2) { // Only draw if there's visible progress 813 | Rect fillRect(rect.x, rect.y, fillWidth, rect.height); 814 | 815 | Color fillColor(100, 150, 255); // Blue progress color 816 | 817 | drawRoundedRect(fillRect, radius, fillColor, true); 818 | 819 | if (rect.height > 10) { 820 | Rect highlightRect(rect.x + 2, rect.y + 2, fillWidth - 4, rect.height / 3); 821 | Color highlightColor( 822 | std::clamp(fillColor.r + 40, 0, 255), 823 | std::clamp(fillColor.g + 40, 0, 255), 824 | std::clamp(fillColor.b + 40, 0, 255), 825 | 150 826 | ); 827 | if (highlightRect.width > 0) { 828 | drawRect(highlightRect, highlightColor, true); 829 | } 830 | } 831 | } 832 | 833 | Color borderColor(80, 80, 80); 834 | if (theme.windowBackground.r > 128) { // Light theme 835 | borderColor = Color(180, 180, 180); 836 | } 837 | drawRoundedRect(rect, radius, borderColor, false); 838 | } 839 | 840 | void RenderManager::renderHexViewer( 841 | const std::vector& hexLines, 842 | const std::string& headerLine, 843 | int scrollPos, 844 | int maxScrollPos, 845 | bool scrollbarHovered, 846 | bool scrollbarPressed, 847 | const Rect& scrollbarRect, 848 | const Rect& thumbRect, 849 | bool darkMode, 850 | int editingRow, 851 | int editingCol, 852 | const std::string& editBuffer, 853 | long long cursorBytePos, 854 | int cursorNibblePos, 855 | long long totalBytes) 856 | { 857 | currentTheme = darkMode ? Theme::Dark() : Theme::Light(); 858 | LayoutMetrics layout; 859 | 860 | #ifdef _WIN32 861 | if (memDC) { 862 | SIZE textSize; 863 | if (GetTextExtentPoint32A(memDC, "0", 1, &textSize)) { 864 | layout.charWidth = (float)textSize.cx; 865 | layout.lineHeight = (float)textSize.cy; 866 | } 867 | else { 868 | layout.charWidth = 8.0f; 869 | layout.lineHeight = 16.0f; 870 | } 871 | } 872 | else { 873 | layout.charWidth = 8.0f; 874 | layout.lineHeight = 16.0f; 875 | } 876 | #elif __APPLE__ 877 | layout.charWidth = 9.6f; 878 | layout.lineHeight = 20.0f; 879 | #else 880 | if (fontInfo) { 881 | layout.charWidth = (float)fontInfo->max_bounds.width; 882 | layout.lineHeight = (float)(fontInfo->ascent + fontInfo->descent); 883 | } 884 | else { 885 | layout.charWidth = 9.6f; 886 | layout.lineHeight = 20.0f; 887 | } 888 | #endif 889 | 890 | _bytesPerLine = 16; 891 | _startByte = scrollPos * _bytesPerLine; 892 | _bytePos = cursorBytePos; 893 | _byteCharacterPos = cursorNibblePos; 894 | _charWidth = (int)layout.charWidth; 895 | _charHeight = (int)layout.lineHeight; 896 | 897 | beginFrame(); 898 | int menuBarHeight = 24; 899 | 900 | Rect contentArea(0, menuBarHeight, windowWidth, windowHeight - menuBarHeight); 901 | drawRect(contentArea, currentTheme.windowBackground, true); 902 | 903 | int disasmColumnWidth = 300; 904 | 905 | _hexAreaX = (int)(layout.margin + (10 * layout.charWidth)); 906 | _hexAreaY = menuBarHeight + (int)(layout.margin + layout.headerHeight + 2); 907 | 908 | if (!headerLine.empty()) { 909 | drawText(headerLine, (int)layout.margin, menuBarHeight + (int)layout.margin, currentTheme.headerColor); 910 | 911 | int disasmX = windowWidth - (int)layout.scrollbarWidth - disasmColumnWidth + 10; 912 | drawText("Disassembly", disasmX, menuBarHeight + (int)layout.margin, currentTheme.disassemblyColor); 913 | 914 | drawLine((int)layout.margin, 915 | menuBarHeight + (int)(layout.margin + layout.headerHeight), 916 | windowWidth - (int)layout.scrollbarWidth, 917 | menuBarHeight + (int)(layout.margin + layout.headerHeight), 918 | currentTheme.separator); 919 | } 920 | 921 | int separatorX = windowWidth - (int)layout.scrollbarWidth - disasmColumnWidth; 922 | drawLine(separatorX, 923 | menuBarHeight + (int)(layout.margin + layout.headerHeight), 924 | separatorX, 925 | windowHeight - (int)layout.margin, 926 | currentTheme.separator); 927 | 928 | int contentY = _hexAreaY; 929 | int contentHeight = windowHeight - contentY - (int)layout.margin; 930 | size_t maxVisibleLines = (size_t)(contentHeight / layout.lineHeight); 931 | _visibleLines = (int)maxVisibleLines; 932 | 933 | size_t startLine = scrollPos; 934 | size_t endLine = std::clamp( 935 | startLine + maxVisibleLines, 936 | size_t{ 0 }, 937 | hexLines.size() 938 | ); 939 | 940 | for (size_t i = startLine; i < endLine; i++) { 941 | int y = contentY + (int)((i - startLine) * layout.lineHeight); 942 | const std::string& line = hexLines[i]; 943 | size_t disasmStart = line.rfind(" "); 944 | std::string hexPart; 945 | std::string disasmPart; 946 | 947 | if (disasmStart != std::string::npos) { 948 | hexPart = line.substr(0, disasmStart); 949 | disasmPart = line.substr(disasmStart + 6); 950 | } 951 | else { 952 | hexPart = line; 953 | } 954 | 955 | drawText(hexPart, (int)layout.margin, y, currentTheme.textColor); 956 | if (!disasmPart.empty()) { 957 | int disasmX = separatorX + 10; 958 | drawText(disasmPart, disasmX, y, currentTheme.disassemblyColor); 959 | } 960 | } 961 | if (_bytePos >= _startByte && _bytePos < _startByte + (_bytesPerLine * _visibleLines)) { 962 | DrawCaret(); 963 | } 964 | if (_selectionLength > 0) { 965 | } 966 | 967 | if (maxScrollPos > 0) { 968 | drawRect(scrollbarRect, currentTheme.scrollbarBg, true); 969 | 970 | Color thumbColor = currentTheme.scrollbarThumb; 971 | if (scrollbarHovered || scrollbarPressed) { 972 | thumbColor.a = 200; 973 | } 974 | drawRect(thumbRect, thumbColor, true); 975 | } 976 | 977 | endFrame(); 978 | } 979 | -------------------------------------------------------------------------------- /HexViewer/src/ui/about.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #include 4 | #elif __APPLE__ 5 | #import 6 | #import 7 | #elif __linux__ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | RenderManager* AboutDialog::renderer = nullptr; 21 | bool AboutDialog::darkMode = true; 22 | NativeWindow AboutDialog::parentWindow = 0; 23 | int AboutDialog::hoveredButton = 0; 24 | int AboutDialog::pressedButton = 0; 25 | Rect AboutDialog::updateButtonRect = {}; 26 | Rect AboutDialog::closeButtonRect = {}; 27 | bool AboutDialog::betaEnabled = false; 28 | Rect AboutDialog::betaToggleRect = {}; 29 | bool AboutDialog::betaToggleHovered = false; 30 | 31 | unsigned char* logoPixels = nullptr; 32 | int logoPixelsWidth = 0; 33 | int logoPixelsHeight = 0; 34 | 35 | #ifdef _WIN32 36 | static HBITMAP logoBitmap = nullptr; 37 | int logoWidth = 0; 38 | int logoHeight = 0; 39 | #elif __APPLE__ 40 | static NSImage* logoImage = nil; 41 | #elif __linux__ 42 | static Pixmap logoPixmap = 0; 43 | static int logoWidth = 0; 44 | static int logoHeight = 0; 45 | static Display* display = nullptr; 46 | static Window window = 0; 47 | static Atom wmDeleteWindow; 48 | #endif 49 | 50 | 51 | void AboutDialog::Show(NativeWindow parent, bool isDarkMode) { 52 | darkMode = isDarkMode; 53 | parentWindow = parent; 54 | hoveredButton = 0; 55 | pressedButton = 0; 56 | betaToggleHovered = false; 57 | 58 | int width = 550; 59 | int height = 480; 60 | 61 | #ifdef _WIN32 62 | WNDCLASSEXW wcex = {}; 63 | wcex.cbSize = sizeof(WNDCLASSEX); 64 | wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 65 | wcex.lpfnWndProc = DialogProc; 66 | wcex.hInstance = GetModuleHandle(nullptr); 67 | wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); 68 | wcex.hbrBackground = nullptr; 69 | wcex.lpszClassName = L"AboutDialogClass"; 70 | 71 | RegisterClassExW(&wcex); 72 | 73 | HWND hWnd = CreateWindowExW( 74 | WS_EX_DLGMODALFRAME | WS_EX_TOPMOST, 75 | L"AboutDialogClass", 76 | L"About HexViewer", 77 | WS_POPUP | WS_CAPTION | WS_SYSMENU, 78 | CW_USEDEFAULT, CW_USEDEFAULT, 79 | width, height, 80 | (HWND)parentWindow, 81 | nullptr, 82 | GetModuleHandle(nullptr), 83 | nullptr 84 | ); 85 | if (!hWnd) return; 86 | 87 | ApplyDarkTitleBar(hWnd, isDarkMode); 88 | 89 | RECT parentRect; 90 | GetWindowRect((HWND)parentWindow, &parentRect); 91 | int x = parentRect.left + (parentRect.right - parentRect.left - width) / 2; 92 | int y = parentRect.top + (parentRect.bottom - parentRect.top - height) / 2; 93 | SetWindowPos(hWnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); 94 | 95 | renderer = new RenderManager(); 96 | if (!renderer->initialize(hWnd)) { 97 | DestroyWindow(hWnd); 98 | delete renderer; 99 | return; 100 | } 101 | RECT rc; 102 | GetClientRect(hWnd, &rc); 103 | renderer->resize(rc.right - rc.left, rc.bottom - rc.top); 104 | 105 | std::vector decoded; 106 | uint32_t decodeWidth = 0, decodeHeight = 0; 107 | 108 | constexpr int DESIRED_LOGO_WIDTH = 100; 109 | constexpr int DESIRED_LOGO_HEIGHT = 100; 110 | 111 | if (ImageLoader::LoadPNGDecoded(reinterpret_cast(MAKEINTRESOURCE(IDB_LOGO)), 112 | decoded, decodeWidth, decodeHeight)) { 113 | if (decodeWidth != DESIRED_LOGO_WIDTH || decodeHeight != DESIRED_LOGO_HEIGHT) { 114 | std::vector resized(DESIRED_LOGO_WIDTH * DESIRED_LOGO_HEIGHT * 4); 115 | 116 | auto clamp = [](int value, int minVal, int maxVal) { 117 | return (value < minVal) ? minVal : (value > maxVal ? maxVal : value); 118 | }; 119 | 120 | for (int y = 0; y < DESIRED_LOGO_HEIGHT; ++y) { 121 | float srcY = float(y) * decodeHeight / DESIRED_LOGO_HEIGHT; 122 | int y0 = clamp(int(srcY), 0, int(decodeHeight) - 1); 123 | int y1 = clamp(y0 + 1, 0, int(decodeHeight) - 1); 124 | float fy = srcY - y0; 125 | 126 | for (int x = 0; x < DESIRED_LOGO_WIDTH; ++x) { 127 | float srcX = float(x) * decodeWidth / DESIRED_LOGO_WIDTH; 128 | int x0 = clamp(int(srcX), 0, int(decodeWidth) - 1); 129 | int x1 = clamp(x0 + 1, 0, int(decodeWidth) - 1); 130 | float fx = srcX - x0; 131 | 132 | for (int c = 0; c < 4; ++c) { 133 | float val = 134 | decoded[(y0 * decodeWidth + x0) * 4 + c] * (1 - fx) * (1 - fy) + 135 | decoded[(y0 * decodeWidth + x1) * 4 + c] * fx * (1 - fy) + 136 | decoded[(y1 * decodeWidth + x0) * 4 + c] * (1 - fx) * fy + 137 | decoded[(y1 * decodeWidth + x1) * 4 + c] * fx * fy; 138 | 139 | resized[(y * DESIRED_LOGO_WIDTH + x) * 4 + c] = uint8_t(val); 140 | } 141 | } 142 | } 143 | 144 | decoded = std::move(resized); 145 | decodeWidth = DESIRED_LOGO_WIDTH; 146 | decodeHeight = DESIRED_LOGO_HEIGHT; 147 | } 148 | 149 | logoBitmap = ImageLoader::CreateHBITMAP(decoded, 150 | int(decodeWidth), 151 | int(decodeHeight)); 152 | logoWidth = int(decodeWidth); 153 | logoHeight = int(decodeHeight); 154 | } 155 | 156 | ShowWindow(hWnd, SW_SHOW); 157 | UpdateWindow(hWnd); 158 | MSG msg; 159 | while (GetMessage(&msg, nullptr, 0, 0)) { 160 | if (msg.message == WM_QUIT || !IsWindow(hWnd)) break; 161 | TranslateMessage(&msg); 162 | DispatchMessage(&msg); 163 | } 164 | 165 | if (logoBitmap) { 166 | DeleteObject(logoBitmap); 167 | logoBitmap = nullptr; 168 | } 169 | delete renderer; 170 | renderer = nullptr; 171 | UnregisterClassW(L"AboutDialogClass", GetModuleHandle(nullptr)); 172 | 173 | #elif __APPLE__ 174 | @autoreleasepool{ 175 | NSRect frame = NSMakeRect(0, 0, width, height); 176 | NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; 177 | NSWindow* nsWindow = [[NSWindow alloc]initWithContentRect:frame styleMask : styleMask backing : NSBackingStoreBuffered defer : NO]; 178 | [nsWindow setTitle:@"About HexViewer"] ; 179 | [nsWindow setLevel:NSFloatingWindowLevel] ; 180 | [nsWindow center] ; 181 | 182 | renderer = new RenderManager(); 183 | if (!renderer->initialize((NativeWindow)nsWindow)) { 184 | [nsWindow close] ; 185 | delete renderer; 186 | return; 187 | } 188 | renderer->resize(width, height); 189 | 190 | std::vector logoData; 191 | if (ImageLoader::LoadPNG("about.png", logoData)) { 192 | NSData* data = [NSData dataWithBytes:logoData.data() length : logoData.size()]; 193 | logoImage = [[NSImage alloc]initWithData:data]; 194 | } 195 | 196 | [nsWindow makeKeyAndOrderFront:nil]; 197 | 198 | bool running = true; 199 | while (running) { 200 | @autoreleasepool { 201 | NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny 202 | untilDate : [NSDate distantPast] 203 | inMode : NSDefaultRunLoopMode 204 | dequeue : YES]; 205 | if (event) { 206 | NSPoint point = [event locationInWindow]; 207 | switch ([event type]) { 208 | case NSEventTypeLeftMouseDown: OnMouseDown((int)point.x,(int)point.y); break; 209 | case NSEventTypeLeftMouseUp: if (OnMouseUp((int)point.x,(int)point.y)) running = false; break; 210 | case NSEventTypeMouseMoved: 211 | case NSEventTypeLeftMouseDragged: OnMouseMove((int)point.x,(int)point.y); break; 212 | default: [NSApp sendEvent:event] ; break; 213 | } 214 | } 215 | if (![nsWindow isVisible]) running = false; 216 | OnPaint(); 217 | } 218 | } 219 | 220 | if (logoImage) { 221 | [logoImage release] ; 222 | logoImage = nullptr; 223 | } 224 | delete renderer; 225 | renderer = nullptr; 226 | [nsWindow close] ; 227 | } 228 | 229 | #elif __linux__ 230 | display = XOpenDisplay(nullptr); 231 | if (!display) return; 232 | 233 | int screen = DefaultScreen(display); 234 | Window root = RootWindow(display, screen); 235 | XSetWindowAttributes attrs = {}; 236 | attrs.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | 237 | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask; 238 | attrs.background_pixel = WhitePixel(display, screen); 239 | 240 | window = XCreateWindow(display, root, 0, 0, width, height, 0, CopyFromParent, 241 | InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &attrs); 242 | XStoreName(display, window, "About HexViewer"); 243 | 244 | Atom wmWindowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); 245 | Atom wmWindowTypeDialog = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); 246 | XChangeProperty(display, window, wmWindowType, XA_ATOM, 32, PropModeReplace, 247 | (unsigned char*)&wmWindowTypeDialog, 1); 248 | 249 | XSizeHints* sizeHints = XAllocSizeHints(); 250 | sizeHints->flags = PMinSize | PMaxSize; 251 | sizeHints->min_width = sizeHints->max_width = width; 252 | sizeHints->min_height = sizeHints->max_height = height; 253 | XSetWMNormalHints(display, window, sizeHints); 254 | XFree(sizeHints); 255 | 256 | wmDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False); 257 | XSetWMProtocols(display, window, &wmDeleteWindow, 1); 258 | 259 | int screenWidth = DisplayWidth(display, screen); 260 | int screenHeight = DisplayHeight(display, screen); 261 | XMoveWindow(display, window, (screenWidth - width) / 2, (screenHeight - height) / 2); 262 | XMapWindow(display, window); 263 | XFlush(display); 264 | 265 | renderer = new RenderManager(); 266 | if (!renderer->initialize((NativeWindow)window)) { 267 | XDestroyWindow(display, window); 268 | XCloseDisplay(display); 269 | delete renderer; 270 | return; 271 | } 272 | renderer->resize(width, height); 273 | 274 | std::vector decoded; 275 | uint32_t decodeWidth = 0, decodeHeight = 0; 276 | 277 | constexpr int DESIRED_LOGO_WIDTH = 100; 278 | constexpr int DESIRED_LOGO_HEIGHT = 100; 279 | 280 | if (ImageLoader::LoadPNGDecoded("about.png", decoded, decodeWidth, decodeHeight)) { 281 | if (decodeWidth != DESIRED_LOGO_WIDTH || decodeHeight != DESIRED_LOGO_HEIGHT) { 282 | std::vector resized(DESIRED_LOGO_WIDTH * DESIRED_LOGO_HEIGHT * 4); 283 | 284 | auto clamp = [](int value, int minVal, int maxVal) { 285 | return (value < minVal) ? minVal : (value > maxVal ? maxVal : value); 286 | }; 287 | 288 | for (int y = 0; y < DESIRED_LOGO_HEIGHT; ++y) { 289 | float srcY = float(y) * decodeHeight / DESIRED_LOGO_HEIGHT; 290 | int y0 = clamp(int(srcY), 0, int(decodeHeight) - 1); 291 | int y1 = clamp(y0 + 1, 0, int(decodeHeight) - 1); 292 | float fy = srcY - y0; 293 | 294 | for (int x = 0; x < DESIRED_LOGO_WIDTH; ++x) { 295 | float srcX = float(x) * decodeWidth / DESIRED_LOGO_WIDTH; 296 | int x0 = clamp(int(srcX), 0, int(decodeWidth) - 1); 297 | int x1 = clamp(x0 + 1, 0, int(decodeWidth) - 1); 298 | float fx = srcX - x0; 299 | 300 | for (int c = 0; c < 4; ++c) { 301 | float val = 302 | decoded[(y0 * decodeWidth + x0) * 4 + c] * (1 - fx) * (1 - fy) + 303 | decoded[(y0 * decodeWidth + x1) * 4 + c] * fx * (1 - fy) + 304 | decoded[(y1 * decodeWidth + x0) * 4 + c] * (1 - fx) * fy + 305 | decoded[(y1 * decodeWidth + x1) * 4 + c] * fx * fy; 306 | 307 | resized[(y * DESIRED_LOGO_WIDTH + x) * 4 + c] = uint8_t(val); 308 | } 309 | } 310 | } 311 | 312 | decoded = std::move(resized); 313 | decodeWidth = DESIRED_LOGO_WIDTH; 314 | decodeHeight = DESIRED_LOGO_HEIGHT; 315 | } 316 | 317 | logoWidth = int(decodeWidth); 318 | logoHeight = int(decodeHeight); 319 | 320 | logoPixelsWidth = logoWidth; 321 | logoPixelsHeight = logoHeight; 322 | logoPixels = new unsigned char[decoded.size()]; 323 | std::copy(decoded.begin(), decoded.end(), logoPixels); 324 | 325 | Visual* visual = DefaultVisual(display, screen); 326 | int depth = DefaultDepth(display, screen); 327 | 328 | logoPixmap = XCreatePixmap(display, window, logoWidth, logoHeight, depth); 329 | GC gc = XCreateGC(display, logoPixmap, 0, nullptr); 330 | 331 | Theme theme = isDarkMode ? Theme::Dark() : Theme::Light(); 332 | uint8_t bgR = theme.windowBackground.r; 333 | uint8_t bgG = theme.windowBackground.g; 334 | uint8_t bgB = theme.windowBackground.b; 335 | 336 | XImage* ximage = XCreateImage(display, visual, depth, ZPixmap, 0, nullptr, 337 | logoWidth, logoHeight, 32, 0); 338 | ximage->data = (char*)malloc(logoHeight * ximage->bytes_per_line); 339 | 340 | for (int y = 0; y < logoHeight; y++) { 341 | for (int x = 0; x < logoWidth; x++) { 342 | int idx = (y * logoWidth + x) * 4; 343 | uint8_t r = decoded[idx]; 344 | uint8_t g = decoded[idx + 1]; 345 | uint8_t b = decoded[idx + 2]; 346 | uint8_t a = decoded[idx + 3]; 347 | 348 | r = (r * a + bgR * (255 - a)) / 255; 349 | g = (g * a + bgG * (255 - a)) / 255; 350 | b = (b * a + bgB * (255 - a)) / 255; 351 | 352 | unsigned long pixel = (r << 16) | (g << 8) | b; 353 | XPutPixel(ximage, x, y, pixel); 354 | } 355 | } 356 | 357 | XPutImage(display, logoPixmap, gc, ximage, 0, 0, 0, 0, logoWidth, logoHeight); 358 | 359 | XFreeGC(display, gc); 360 | free(ximage->data); 361 | ximage->data = nullptr; 362 | XDestroyImage(ximage); 363 | } 364 | 365 | bool running = true; 366 | XEvent event; 367 | while (running) { 368 | while (XPending(display)) { 369 | XNextEvent(display, &event); 370 | switch (event.type) { 371 | case Expose: if (event.xexpose.count == 0) OnPaint(); break; 372 | case ClientMessage: if ((Atom)event.xclient.data.l[0] == wmDeleteWindow) running = false; break; 373 | case MotionNotify: OnMouseMove(event.xmotion.x, event.xmotion.y); break; 374 | case ButtonPress: if (event.xbutton.button == Button1) OnMouseDown(event.xbutton.x, event.xbutton.y); break; 375 | case ButtonRelease: if (event.xbutton.button == Button1 && OnMouseUp(event.xbutton.x, event.xbutton.y)) running = false; break; 376 | } 377 | } 378 | usleep(16000); 379 | } 380 | 381 | if (logoPixmap) { 382 | XFreePixmap(display, logoPixmap); 383 | logoPixmap = 0; 384 | } 385 | delete renderer; 386 | renderer = nullptr; 387 | XDestroyWindow(display, window); 388 | XCloseDisplay(display); 389 | 390 | #endif 391 | } 392 | 393 | void AboutDialog::RenderContent(int width, int height) { 394 | if (!renderer) return; 395 | 396 | Theme theme = darkMode ? Theme::Dark() : Theme::Light(); 397 | renderer->beginFrame(); 398 | renderer->clear(theme.windowBackground); 399 | 400 | int logoPadding = 20; 401 | int logoSize = 100; 402 | 403 | #ifdef _WIN32 404 | #elif __APPLE__ 405 | if (logoImage) { 406 | renderer->drawImage(logoImage, logoSize, logoSize, logoPadding, logoPadding); 407 | } 408 | #elif __linux__ 409 | if (logoPixmap) { 410 | renderer->drawX11Pixmap(logoPixmap, logoWidth, logoHeight, logoPadding, logoPadding); 411 | } 412 | #endif 413 | 414 | int contentX = logoPadding + logoSize + 30; 415 | int contentY = logoPadding; 416 | 417 | std::string appName = "HexViewer"; 418 | renderer->drawText(appName, contentX, contentY, theme.textColor); 419 | 420 | std::string version = std::string(Translations::T("Version")) + " 1.0.0"; 421 | renderer->drawText(version, contentX, contentY + 25, theme.disabledText); 422 | 423 | std::string desc = Translations::T("A modern cross-platform hex editor"); 424 | renderer->drawText(desc, contentX, contentY + 50, theme.disabledText); 425 | 426 | int featuresY = logoPadding + logoSize + 30; 427 | renderer->drawText(Translations::T("Features:"), 40, featuresY, theme.textColor); 428 | renderer->drawText(std::string("- ") + Translations::T("Cross-platform support"), 50, featuresY + 30, theme.disabledText); 429 | renderer->drawText(std::string("- ") + Translations::T("Real-time hex editing"), 50, featuresY + 55, theme.disabledText); 430 | renderer->drawText(std::string("- ") + Translations::T("Dark mode support"), 50, featuresY + 80, theme.disabledText); 431 | 432 | renderer->drawLine(0, height - 120, width, height - 120, theme.separator); 433 | 434 | int toggleY = height - 100; 435 | betaToggleRect = Rect(40, toggleY, 200, 25); 436 | 437 | Rect checkboxRect(betaToggleRect.x, betaToggleRect.y, 18, 18); 438 | Color checkboxBorder = betaToggleHovered ? Color(100, 150, 255) : theme.disabledText; 439 | renderer->drawRect(checkboxRect, theme.windowBackground, true); 440 | renderer->drawRect(checkboxRect, checkboxBorder, false); 441 | 442 | if (betaEnabled) { 443 | renderer->drawRect(Rect(checkboxRect.x + 3, checkboxRect.y + 3, 12, 12), 444 | Color(100, 150, 255), true); 445 | } 446 | 447 | renderer->drawText(Translations::T("Include Beta Versions"), 448 | betaToggleRect.x + 23, betaToggleRect.y + 2, theme.disabledText); 449 | 450 | int buttonY = height - 60; 451 | int buttonHeight = 35; 452 | int buttonWidth = 160; 453 | int buttonX = (width - buttonWidth) / 2; 454 | 455 | updateButtonRect = Rect(buttonX, buttonY, buttonWidth, buttonHeight); 456 | WidgetState updateState; 457 | updateState.rect = updateButtonRect; 458 | updateState.enabled = true; 459 | updateState.hovered = (hoveredButton == 1); 460 | updateState.pressed = (pressedButton == 1); 461 | renderer->drawModernButton(updateState, theme, Translations::T("Check for Updates")); 462 | 463 | std::string copyright = "\u00A9 2025 DiE team!"; 464 | int copyrightX = (width - (copyright.length() * 8)) / 2; 465 | renderer->drawText(copyright, copyrightX, height - 20, theme.disabledText); 466 | 467 | renderer->endFrame(); 468 | } 469 | 470 | #ifdef _WIN32 471 | void AboutDialog::OnPaint(HWND hWnd) { 472 | PAINTSTRUCT ps; 473 | HDC hdc = BeginPaint(hWnd, &ps); 474 | 475 | if (renderer) { 476 | RECT rc; 477 | GetClientRect(hWnd, &rc); 478 | 479 | RenderContent(rc.right - rc.left, rc.bottom - rc.top); 480 | 481 | if (logoBitmap) { 482 | int logoPadding = 20; 483 | ImageLoader::DrawTransparentBitmap(hdc, logoBitmap, logoPadding, logoPadding, logoWidth, logoHeight); 484 | } 485 | } 486 | 487 | EndPaint(hWnd, &ps); 488 | } 489 | 490 | void AboutDialog::OnMouseMove(HWND hWnd, int x, int y) { 491 | int oldHovered = hoveredButton; 492 | bool oldBetaHovered = betaToggleHovered; 493 | hoveredButton = 0; 494 | betaToggleHovered = false; 495 | 496 | if (x >= betaToggleRect.x && x <= betaToggleRect.x + betaToggleRect.width && 497 | y >= betaToggleRect.y && y <= betaToggleRect.y + betaToggleRect.height) { 498 | betaToggleHovered = true; 499 | } 500 | 501 | if (x >= updateButtonRect.x && x <= updateButtonRect.x + updateButtonRect.width && 502 | y >= updateButtonRect.y && y <= updateButtonRect.y + updateButtonRect.height) { 503 | hoveredButton = 1; 504 | } 505 | 506 | if (oldHovered != hoveredButton || oldBetaHovered != betaToggleHovered) { 507 | InvalidateRect(hWnd, nullptr, FALSE); 508 | } 509 | } 510 | 511 | void AboutDialog::OnMouseDown(HWND hWnd, int x, int y) { 512 | if (betaToggleHovered) { 513 | betaEnabled = !betaEnabled; 514 | InvalidateRect(hWnd, nullptr, FALSE); 515 | return; 516 | } 517 | 518 | pressedButton = hoveredButton; 519 | InvalidateRect(hWnd, nullptr, FALSE); 520 | } 521 | 522 | bool AboutDialog::OnMouseUp(HWND hWnd, int x, int y) { 523 | if (pressedButton == hoveredButton && pressedButton == 1) { 524 | UpdateInfo info; 525 | info.currentVersion = "1.0.0"; 526 | info.updateAvailable = false; 527 | info.latestVersion = "Checking..."; 528 | info.releaseNotes = "Please wait while we check for updates..."; 529 | info.betaPreference = betaEnabled; // CRITICAL: Pass beta preference 530 | 531 | if (betaEnabled) { 532 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases"; 533 | } else { 534 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases/latest"; 535 | } 536 | 537 | ShowWindow(hWnd, SW_HIDE); 538 | 539 | HWND parentWnd = parentWindow; 540 | bool checkBeta = betaEnabled; 541 | 542 | std::thread([parentWnd, info, checkBeta]() mutable { 543 | std::string response = HttpGet(info.releaseApiUrl); 544 | 545 | if (!response.empty()) { 546 | std::string releaseToCheck = response; 547 | 548 | if (checkBeta && response.length() > 0 && response[0] == '[') { 549 | size_t firstBrace = response.find("{\""); 550 | if (firstBrace != std::string::npos) { 551 | int braceCount = 0; 552 | size_t endPos = firstBrace; 553 | 554 | for (size_t i = firstBrace; i < response.length(); i++) { 555 | if (response[i] == '{') braceCount++; 556 | if (response[i] == '}') { 557 | braceCount--; 558 | if (braceCount == 0) { 559 | endPos = i; 560 | break; 561 | } 562 | } 563 | } 564 | 565 | releaseToCheck = response.substr(firstBrace, endPos - firstBrace + 1); 566 | } 567 | } 568 | 569 | info.latestVersion = ExtractJsonValue(releaseToCheck, "tag_name"); 570 | 571 | if (!info.latestVersion.empty() && info.latestVersion[0] == 'v') { 572 | info.latestVersion = info.latestVersion.substr(1); 573 | } 574 | 575 | if (info.latestVersion.empty()) { 576 | std::string releaseName = ExtractJsonValue(releaseToCheck, "name"); 577 | size_t vPos = releaseName.find("v"); 578 | if (vPos != std::string::npos) { 579 | info.latestVersion = releaseName.substr(vPos + 1); 580 | size_t spacePos = info.latestVersion.find(" "); 581 | if (spacePos != std::string::npos) { 582 | info.latestVersion = info.latestVersion.substr(0, spacePos); 583 | } 584 | } 585 | } 586 | 587 | info.releaseNotes = ExtractJsonValue(releaseToCheck, "body"); 588 | 589 | size_t pos = 0; 590 | while ((pos = info.releaseNotes.find("\xE2\x80\x94", pos)) != std::string::npos) { 591 | info.releaseNotes.replace(pos, 3, "-"); 592 | } 593 | 594 | info.updateAvailable = (info.latestVersion != info.currentVersion && 595 | !info.latestVersion.empty()); 596 | 597 | if (checkBeta) { 598 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases"; 599 | } 600 | } else { 601 | info.updateAvailable = false; 602 | info.latestVersion = info.currentVersion; 603 | info.releaseNotes = "Unable to check for updates. Please try again later."; 604 | } 605 | 606 | UpdateDialog::Show(parentWnd, info); 607 | }).detach(); 608 | 609 | DestroyWindow(hWnd); 610 | return true; 611 | } 612 | 613 | pressedButton = 0; 614 | InvalidateRect(hWnd, nullptr, FALSE); 615 | return false; 616 | } 617 | 618 | 619 | LRESULT CALLBACK AboutDialog::DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { 620 | switch (message) { 621 | case WM_ERASEBKGND: 622 | return 1; 623 | 624 | case WM_PAINT: 625 | OnPaint(hWnd); 626 | return 0; 627 | 628 | case WM_MOUSEMOVE: 629 | OnMouseMove(hWnd, LOWORD(lParam), HIWORD(lParam)); 630 | break; 631 | 632 | case WM_LBUTTONDOWN: 633 | OnMouseDown(hWnd, LOWORD(lParam), HIWORD(lParam)); 634 | break; 635 | 636 | case WM_LBUTTONUP: 637 | OnMouseUp(hWnd, LOWORD(lParam), HIWORD(lParam)); 638 | break; 639 | 640 | case WM_CLOSE: 641 | DestroyWindow(hWnd); 642 | return 0; 643 | 644 | case WM_DESTROY: 645 | PostQuitMessage(0); 646 | return 0; 647 | } 648 | 649 | return DefWindowProc(hWnd, message, wParam, lParam); 650 | } 651 | 652 | #elif __linux__ 653 | 654 | void AboutDialog::OnPaint() { 655 | if (!renderer) return; 656 | RenderContent(550, 480); 657 | XClearWindow(display, window); 658 | XSync(display, False); 659 | } 660 | 661 | void AboutDialog::OnMouseMove(int x, int y) { 662 | int oldHovered = hoveredButton; 663 | bool oldBetaHovered = betaToggleHovered; 664 | hoveredButton = 0; 665 | betaToggleHovered = false; 666 | 667 | if (x >= betaToggleRect.x && x <= betaToggleRect.x + betaToggleRect.width && 668 | y >= betaToggleRect.y && y <= betaToggleRect.y + betaToggleRect.height) { 669 | betaToggleHovered = true; 670 | } 671 | 672 | if (x >= updateButtonRect.x && x <= updateButtonRect.x + updateButtonRect.width && 673 | y >= updateButtonRect.y && y <= updateButtonRect.y + updateButtonRect.height) { 674 | hoveredButton = 1; 675 | } 676 | 677 | if (oldHovered != hoveredButton || oldBetaHovered != betaToggleHovered) { 678 | XClearWindow(display, window); 679 | XEvent exposeEvent; 680 | memset(&exposeEvent, 0, sizeof(exposeEvent)); 681 | exposeEvent.type = Expose; 682 | exposeEvent.xexpose.window = window; 683 | exposeEvent.xexpose.count = 0; 684 | XSendEvent(display, window, False, ExposureMask, &exposeEvent); 685 | XFlush(display); 686 | } 687 | } 688 | 689 | void AboutDialog::OnMouseDown(int x, int y) { 690 | if (betaToggleHovered) { 691 | betaEnabled = !betaEnabled; 692 | XClearWindow(display, window); 693 | XEvent exposeEvent; 694 | memset(&exposeEvent, 0, sizeof(exposeEvent)); 695 | exposeEvent.type = Expose; 696 | exposeEvent.xexpose.window = window; 697 | exposeEvent.xexpose.count = 0; 698 | XSendEvent(display, window, False, ExposureMask, &exposeEvent); 699 | XFlush(display); 700 | return; 701 | } 702 | 703 | pressedButton = hoveredButton; 704 | XClearWindow(display, window); 705 | XEvent exposeEvent; 706 | memset(&exposeEvent, 0, sizeof(exposeEvent)); 707 | exposeEvent.type = Expose; 708 | exposeEvent.xexpose.window = window; 709 | exposeEvent.xexpose.count = 0; 710 | XSendEvent(display, window, False, ExposureMask, &exposeEvent); 711 | XFlush(display); 712 | } 713 | 714 | bool AboutDialog::OnMouseUp(int x, int y) { 715 | if (pressedButton == hoveredButton && pressedButton == 1) { 716 | UpdateInfo info; 717 | info.currentVersion = "1.0.0"; 718 | 719 | if (betaEnabled) { 720 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases"; 721 | } 722 | else { 723 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases/latest"; 724 | } 725 | 726 | std::string response = HttpGet(info.releaseApiUrl); 727 | 728 | if (!response.empty()) { 729 | std::string releaseName; 730 | std::string releaseJson = response; 731 | 732 | if (checkBeta) { 733 | size_t releaseStart = response.find("["); 734 | if (releaseStart != std::string::npos) { 735 | size_t firstObjStart = response.find("{", releaseStart); 736 | if (firstObjStart != std::string::npos) { 737 | size_t firstObjEnd = response.find("},{", firstObjStart); 738 | if (firstObjEnd == std::string::npos) { 739 | firstObjEnd = response.find("}]", firstObjStart); 740 | } 741 | if (firstObjEnd != std::string::npos) { 742 | releaseJson = response.substr(firstObjStart, firstObjEnd - firstObjStart + 1); 743 | } 744 | } 745 | } 746 | } 747 | 748 | info.latestVersion = ExtractJsonValue(releaseJson, "tag_name"); 749 | if (!info.latestVersion.empty() && info.latestVersion[0] == 'v') { 750 | info.latestVersion = info.latestVersion.substr(1); 751 | } 752 | 753 | if (info.latestVersion.empty()) { 754 | releaseName = ExtractJsonValue(releaseJson, "name"); 755 | size_t vPos = releaseName.find("v"); 756 | if (vPos != std::string::npos) { 757 | size_t spacePos = releaseName.find(" ", vPos); 758 | if (spacePos != std::string::npos) { 759 | info.latestVersion = releaseName.substr(vPos + 1, spacePos - vPos - 1); 760 | } 761 | else { 762 | info.latestVersion = releaseName.substr(vPos + 1); 763 | } 764 | } 765 | } 766 | 767 | info.releaseNotes = ExtractJsonValue(releaseJson, "body"); 768 | info.updateAvailable = (info.latestVersion != info.currentVersion && !info.latestVersion.empty()); 769 | } 770 | else { 771 | info.updateAvailable = false; 772 | info.latestVersion = info.currentVersion; 773 | info.releaseNotes = "Unable to check for updates. Please try again later."; 774 | } 775 | 776 | UpdateDialog::Show(parentWindow, info); 777 | return true; 778 | } 779 | 780 | pressedButton = 0; 781 | XClearWindow(display, window); 782 | XEvent exposeEvent; 783 | memset(&exposeEvent, 0, sizeof(exposeEvent)); 784 | exposeEvent.type = Expose; 785 | exposeEvent.xexpose.window = window; 786 | exposeEvent.xexpose.count = 0; 787 | XSendEvent(display, window, False, ExposureMask, &exposeEvent); 788 | XFlush(display); 789 | 790 | return false; 791 | } 792 | 793 | #elif __APPLE__ 794 | 795 | void AboutDialog::OnPaint() { 796 | if (!renderer) return; 797 | RenderContent(550, 480); 798 | @autoreleasepool { 799 | [[nsWindow contentView]setNeedsDisplay:YES]; 800 | } 801 | } 802 | 803 | void AboutDialog::OnMouseMove(int x, int y) { 804 | int oldHovered = hoveredButton; 805 | bool oldBetaHovered = betaToggleHovered; 806 | hoveredButton = 0; 807 | betaToggleHovered = false; 808 | 809 | if (x >= betaToggleRect.x && x <= betaToggleRect.x + betaToggleRect.width && 810 | y >= betaToggleRect.y && y <= betaToggleRect.y + betaToggleRect.height) { 811 | betaToggleHovered = true; 812 | } 813 | 814 | if (x >= updateButtonRect.x && x <= updateButtonRect.x + updateButtonRect.width && 815 | y >= updateButtonRect.y && y <= updateButtonRect.y + updateButtonRect.height) { 816 | hoveredButton = 1; 817 | } 818 | 819 | if (oldHovered != hoveredButton || oldBetaHovered != betaToggleHovered) { 820 | OnPaint(); 821 | } 822 | } 823 | 824 | void AboutDialog::OnMouseDown(int x, int y) { 825 | if (betaToggleHovered) { 826 | betaEnabled = !betaEnabled; 827 | OnPaint(); 828 | return; 829 | } 830 | 831 | pressedButton = hoveredButton; 832 | OnPaint(); 833 | } 834 | 835 | bool AboutDialog::OnMouseUp(int x, int y) { 836 | if (pressedButton == hoveredButton && pressedButton == 1) { 837 | UpdateInfo info; 838 | info.currentVersion = "1.0.0"; 839 | 840 | if (betaEnabled) { 841 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases"; 842 | } 843 | else { 844 | info.releaseApiUrl = "https://api.github.com/repos/horsicq/HexViewer/releases/latest"; 845 | } 846 | 847 | std::string response = HttpGet(info.releaseApiUrl); 848 | 849 | if (!response.empty()) { 850 | if (betaEnabled) { 851 | size_t releaseStart = response.find("{\"url\""); 852 | if (releaseStart != std::string::npos) { 853 | std::string firstRelease = response.substr(releaseStart); 854 | size_t releaseEnd = firstRelease.find("},"); 855 | if (releaseEnd == std::string::npos) { 856 | releaseEnd = firstRelease.find("}]"); 857 | } 858 | if (releaseEnd != std::string::npos) { 859 | response = firstRelease.substr(0, releaseEnd + 1); 860 | } 861 | } 862 | } 863 | 864 | info.latestVersion = ExtractJsonValue(response, "tag_name"); 865 | if (!info.latestVersion.empty() && info.latestVersion[0] == 'v') { 866 | info.latestVersion = info.latestVersion.substr(1); 867 | } 868 | 869 | info.releaseNotes = ExtractJsonValue(response, "body"); 870 | info.updateAvailable = (info.latestVersion != info.currentVersion && !info.latestVersion.empty()); 871 | } 872 | else { 873 | info.updateAvailable = false; 874 | info.latestVersion = info.currentVersion; 875 | info.releaseNotes = "Unable to check for updates. Please try again later."; 876 | } 877 | 878 | UpdateDialog::Show(parentWindow, info); 879 | return true; 880 | } 881 | 882 | pressedButton = 0; 883 | OnPaint(); 884 | return false; 885 | } 886 | 887 | #endif 888 | --------------------------------------------------------------------------------