Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/dev-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libwayland-dev libxkbcommon-dev

sudo apt-get install -y build-essential cmake libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libwayland-dev libxkbcommon-dev libcurl4-openssl-dev
- name: Configure and build (Linux)
if: runner.os == 'Linux'
run: |
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make

- name: Configure and build (Windows)
if: runner.os == 'Windows'
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libwayland-dev libxkbcommon-dev
sudo apt-get install -y build-essential cmake libgl1-mesa-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libwayland-dev libxkbcommon-dev libcurl4-openssl-dev

- name: Configure and build (Linux)
if: runner.os == 'Linux'
Expand Down
19 changes: 17 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ set(IMGUI_BACKEND_SRC
external/imgui/backends/imgui_impl_opengl3.cpp
)

if(WIN32)
# fetch curl
include(FetchContent)
FetchContent_Declare(
curl
URL https://curl.se/download/curl-8.5.0.tar.gz
DOWNLOAD_EXTRACT_TIMESTAMP true
OVERRIDE_FIND_PACKAGE
)
FetchContent_MakeAvailable(curl)
else()
# On Linux, we can use the system's libcurl
find_package(CURL REQUIRED)
endif()

# --- IMGUI ---
add_library(imgui STATIC ${IMGUI_CORE_SRC} ${IMGUI_BACKEND_SRC})
target_include_directories(imgui PUBLIC external/imgui)
Expand Down Expand Up @@ -58,9 +73,9 @@ target_include_directories(ui PUBLIC include external/imgui external/ImGuiFileDi
target_link_libraries(ui PUBLIC core imgui glad glfw imguifiledialog imgui_gradient)

# --- App library ---
add_library(app STATIC src/app.cpp)
add_library(app STATIC src/app.cpp src/update.cpp)
target_include_directories(app PUBLIC include ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(app PUBLIC core cache ui imgui glad glfw hex_display_feature_manager)
target_link_libraries(app PUBLIC core cache ui imgui glad glfw hex_display_feature_manager CURL::libcurl)

if(WIN32)
add_executable(EntropyVisualizer WIN32 main.cpp)
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.8.2
1.9.0
8 changes: 8 additions & 0 deletions include/entropy/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <imgui_gradient/imgui_gradient.hpp>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
Expand All @@ -32,6 +33,13 @@ struct UiState {
bool showSearchWindow = false;
bool showFeatureSettings = false;
bool showGeneralSettings = false;
// Update check state
std::atomic<bool> updateChecked{false};
std::atomic<bool> updateAvailable{false};
std::atomic<bool> updateManualRequest{false};
std::string latestVersion;
std::string updateUrl;
std::mutex updateMutex;
size_t highlighted_sector = SIZE_MAX;
std::vector<uint8_t> currentSectorData;
size_t currentSectorIndex = 0;
Expand Down
2 changes: 1 addition & 1 deletion include/entropy/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const size_t DEFAULT_BLOCK_WIDTH = 256;
const size_t DEFAULT_BLOCK_HEIGHT = 256;

const std::string ABOUT_STRING =
"EntropyVisualizer\nMatthias Hüppi, maede97@hotmail.com\nVersion " + std::string(VERSION) + " - " + std::string(DATE);
"EntropyVisualizer\nMatthias Hüppi, maede97@hotmail.com\nVersion " + std::string(EV_VERSION) + " - " + std::string(EV_DATE);
const std::string HELP_STRING = R"(
Entropy Visualizer - User Guide

Expand Down
1 change: 1 addition & 0 deletions include/entropy/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void renderAboutWindow(UiState &uiState);
void renderHelpWindow(UiState &uiState);
void renderHexViewWindow(UiState &uiState, const AppState &appState);
void renderSearchWindow(UiState &uiState, AppState &appState, std::function<void(size_t)> loadHexData);
void renderUpdateWindow(UiState &uiState);
void handleFileDialogs(UiState &uiState, AppState &appState, IGFD::FileDialogConfig &config, std::function<void(size_t)> loadHexData);
void renderVisualization(ImDrawList *draw_list, GLuint tex, const std::vector<uint8_t> &block_buffer, float zoom, ImVec2 pan_offset,
size_t current_block, size_t block_size, size_t block_width, size_t block_height, UiState &uiState,
Expand Down
10 changes: 10 additions & 0 deletions include/entropy/update.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <entropy/app.h>
#include <string>

namespace entropy {

void startUpdateCheck(UiState &uiState, const std::string &file_path, bool manual = false);

} // namespace entropy
8 changes: 6 additions & 2 deletions include/entropy/version.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#pragma once

#define VERSION "1.8.2"
#define DATE "10.02.2026"
namespace entropy {

#define EV_VERSION "1.9.0"
#define EV_DATE "09.03.2026"

} // namespace entropy
4 changes: 4 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <entropy/core.h>
#include <entropy/icon.h>
#include <entropy/ui.h>
#include <entropy/update.h>
#include <entropy/version.h>

entropy::AppState *globalAppState = nullptr;
Expand Down Expand Up @@ -48,6 +49,9 @@ int main(int argc, char **argv) {
return 1;
}

// Start background update check (non-blocking)
entropy::startUpdateCheck(uiState, "VERSION.txt");

// Register ImGui settings handler for features
ImGuiSettingsHandler featuresHandler;
featuresHandler.TypeName = "HexDisplayFeatures";
Expand Down
18 changes: 16 additions & 2 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <entropy/app.h>
#include <entropy/cache.h>
#include <entropy/icon.h>
#include <entropy/update.h>
#include <entropy/version.h>
#include <iostream>
#include <string>
Expand Down Expand Up @@ -289,7 +290,7 @@ void initializeWindowAndGL(GLFWwindow *&window, GLuint &tex) {
return;

const char *glsl_version = "#version 130";
window = glfwCreateWindow(1800, 1200, (std::string("Entropy Visualizer ") + std::string(VERSION)).c_str(), nullptr, nullptr);
window = glfwCreateWindow(1800, 1200, (std::string("Entropy Visualizer ") + std::string(EV_VERSION)).c_str(), nullptr, nullptr);
if (!window)
return;

Expand Down Expand Up @@ -389,7 +390,7 @@ void mainLoop(GLFWwindow *window, GLuint tex, AppState &state, UiState &uiState,
if (ImGui::MenuItem("Find")) {
uiState.showSearchWindow = true;
}

// Recent files submenu
if (!state.recentCacheFiles.empty() && ImGui::BeginMenu("Recent Cache Files")) {
for (const auto &filePair : state.recentCacheFiles) {
Expand Down Expand Up @@ -428,6 +429,18 @@ void mainLoop(GLFWwindow *window, GLuint tex, AppState &state, UiState &uiState,
}

if (ImGui::BeginMenu("Misc")) {
if (ImGui::MenuItem("Check for Updates")) {
// Reset state and start an explicit check (manual)
uiState.updateChecked = false;
uiState.updateAvailable = false;
uiState.updateManualRequest = true;
{
std::lock_guard<std::mutex> lk(uiState.updateMutex);
uiState.latestVersion.clear();
uiState.updateUrl.clear();
}
startUpdateCheck(uiState, "update/latest_version.txt", true);
}
if (ImGui::MenuItem("About")) {
uiState.showAboutUs = true;
}
Expand Down Expand Up @@ -581,6 +594,7 @@ void mainLoop(GLFWwindow *window, GLuint tex, AppState &state, UiState &uiState,
renderHelpWindow(uiState);
renderHexViewWindow(uiState, state);
renderSearchWindow(uiState, state, loadHexData);
renderUpdateWindow(uiState);

if (uiState.showFeatureSettings) {
ImGui::Begin("Feature Settings", &uiState.showFeatureSettings);
Expand Down
40 changes: 40 additions & 0 deletions src/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <GL/gl.h>
#include <algorithm>
#include <cstdlib>
#include <map>

namespace entropy {
Expand Down Expand Up @@ -40,6 +41,45 @@ void renderAboutWindow(UiState &uiState) {
}
}

void renderUpdateWindow(UiState &uiState) {
if (!uiState.updateChecked)
return;

if (!uiState.updateAvailable && !uiState.updateManualRequest)
return;

std::lock_guard<std::mutex> lk(uiState.updateMutex);
ImGui::SetNextWindowSize(ImVec2(420, 140), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Update", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
if (uiState.updateAvailable) {
ImGui::Text("A new version is available: %s", uiState.latestVersion.c_str());
ImGui::Separator();
if (ImGui::Button("Open Release Page")) {

#ifdef __linux__
std::string cmd = std::string("xdg-open \"") + uiState.updateUrl + "\" &";
#elif _WIN32
std::string cmd = std::string("start \"\" \"") + uiState.updateUrl + "\"";
#endif
std::system(cmd.c_str());
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
uiState.updateAvailable = false;
uiState.updateManualRequest = false;
}
} else {
// Manual request and no update available
ImGui::Text("No updates available. You are running version %s", EV_VERSION);
ImGui::Separator();
if (ImGui::Button("OK")) {
uiState.updateManualRequest = false;
}
}
}
ImGui::End();
}

void renderHelpWindow(UiState &uiState) {
if (uiState.showHelp) {
ImGui::SetNextWindowSize(ImVec2(600, 600), ImGuiCond_FirstUseEver);
Expand Down
114 changes: 114 additions & 0 deletions src/update.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include <entropy/update.h>
#include <entropy/version.h>

#include <chrono>
#include <curl/curl.h>
#include <iostream>
#include <regex>
#include <sstream>
#include <thread>

namespace entropy {

static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total = size * nmemb;
std::string *s = static_cast<std::string *>(userp);
s->append(static_cast<char *>(contents), total);
return total;
}

static bool parse_semver(const std::string &s, int &major, int &minor, int &patch) {
std::regex re(R"((\d+)\.(\d+)\.(\d+))");
std::smatch m;
if (std::regex_search(s, m, re)) {
major = std::stoi(m[1]);
minor = std::stoi(m[2]);
patch = std::stoi(m[3]);
return true;
}
return false;
}

static int compare_semver(const std::string &a, const std::string &b) {
int am = 0, an = 0, ap = 0;
int bm = 0, bn = 0, bp = 0;
if (!parse_semver(a, am, an, ap) || !parse_semver(b, bm, bn, bp)) {
return 0; // unknown; treat as equal
}
if (am != bm)
return (am < bm) ? -1 : 1;
if (an != bn)
return (an < bn) ? -1 : 1;
if (ap != bp)
return (ap < bp) ? -1 : 1;
return 0;
}

void startUpdateCheck(UiState &uiState, const std::string &file_path, bool manual) {
// Mark manual request state
if (manual)
uiState.updateManualRequest = true;

// Run async in a detached thread
std::thread([&uiState, file_path, manual]() {
const std::string base = "https://raw.githubusercontent.com/maede97/EntropyVisualizer/refs/heads/main/";
const std::string full_url = base + file_path;
const std::string release_page = "https://github.com/maede97/EntropyVisualizer/releases";

CURL *curl = curl_easy_init();
if (!curl) {
uiState.updateChecked = true;
return;
}

std::string response;
curl_easy_setopt(curl, CURLOPT_URL, full_url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
// Always mark checked; for manual requests we want the UI to show a result
uiState.updateAvailable = false;
uiState.updateChecked = true;
return;
}

curl_easy_cleanup(curl);

// Trim whitespace
std::string trimmed = response;

std::cout << "Latest version string from server: '" << trimmed << "'" << std::endl;

while (!trimmed.empty() && isspace((unsigned char)trimmed.back()))
trimmed.pop_back();
size_t start = 0;
while (start < trimmed.size() && isspace((unsigned char)trimmed[start]))
start++;
trimmed = (start < trimmed.size()) ? trimmed.substr(start) : std::string();

std::string local_version = EV_VERSION;
std::string remote_version = trimmed;

int cmp = compare_semver(local_version, remote_version);
if (cmp < 0) {
// remote is newer
{
std::lock_guard<std::mutex> lk(uiState.updateMutex);
uiState.latestVersion = remote_version;
uiState.updateUrl = release_page;
}
uiState.updateAvailable = true;
} else {
uiState.updateAvailable = false;
}
// Mark checked so UI can react; manual requests will show the window even when not available
uiState.updateChecked = true;
}).detach();
}

} // namespace entropy