macOS port: build, rendering, daemon, and mining fixes
Build & setup: - Fix setup.sh and build.sh for macOS (bundle daemon, xmrig, sapling params, asmap.dat into .app) - Fix CMakeLists.txt libsodium linking for macOS - Fix incbin.h to use __DATA,__const section on macOS - Remove vendored libsodium-1.0.18 source tree (use fetch script instead) - Remove prebuilt-binaries/xmrig (replaced by xmrig-hac) - Add .DS_Store to .gitignore Rendering & UI: - Use GLSL #version 150 and OpenGL 3.2 Core Profile on macOS - Force dpiScale=1.0 on macOS to fix Retina double-scaling - Set default window/UI opacity to 100% on Mac/Linux - Add scroll fade shader guard for macOS GL compatibility - Add ImGui error recovery around render loop and mining tab Daemon & bootstrap: - Fix getDragonXDataDir() to return ~/Library/Application Support/Hush/DRAGONX/ on macOS - Fix isPortInUse() with connect() fallback (no /proc/net/tcp on macOS) - Increase daemon watchdog timeout from 3s to 15s - Add daemon status indicator (colored dot + label) in wizard bootstrap phases Mining tab: - Fix EmbeddedDaemon::getMemoryUsageMB() crash on macOS (was using Linux /proc) - Fix XmrigManager::getMemoryUsageMB() to use ps on macOS instead of /proc - Restructure RenderMiningTab with wrapper pattern for exception safety - Fix default pool URL to include port (pool.dragonx.is:3433)
This commit is contained in:
60
src/app.cpp
60
src/app.cpp
@@ -1958,13 +1958,29 @@ bool App::startEmbeddedDaemon()
|
||||
if (!exe_dir.empty()) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(daemon_dir, ec);
|
||||
|
||||
// On macOS .app bundles, params are in Contents/Resources/
|
||||
// while the executable is in Contents/MacOS/
|
||||
std::vector<std::string> searchDirs = { exe_dir };
|
||||
#ifdef __APPLE__
|
||||
{
|
||||
fs::path resourcesDir = fs::path(exe_dir).parent_path() / "Resources";
|
||||
if (fs::is_directory(resourcesDir))
|
||||
searchDirs.insert(searchDirs.begin(), resourcesDir.string());
|
||||
}
|
||||
#endif
|
||||
|
||||
for (const char* name : paramFiles) {
|
||||
fs::path src = fs::path(exe_dir) / name;
|
||||
fs::path dst = fs::path(daemon_dir) / name;
|
||||
if (fs::exists(src) && !fs::exists(dst)) {
|
||||
DEBUG_LOGF("Copying bundled %s from exe dir to %s\n", name, daemon_dir.c_str());
|
||||
fs::copy_file(src, dst, ec);
|
||||
if (!ec) copied = true;
|
||||
if (fs::exists(dst)) continue;
|
||||
for (const auto& dir : searchDirs) {
|
||||
fs::path src = fs::path(dir) / name;
|
||||
if (fs::exists(src)) {
|
||||
DEBUG_LOGF("Copying bundled %s from %s to %s\n", name, dir.c_str(), daemon_dir.c_str());
|
||||
fs::copy_file(src, dst, ec);
|
||||
if (!ec) copied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1979,6 +1995,40 @@ bool App::startEmbeddedDaemon()
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure asmap.dat and daemon binaries are copied from the bundle
|
||||
// to the daemon directory even if sapling params already existed.
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
std::string exe_dir = util::Platform::getExecutableDirectory();
|
||||
std::string daemon_dir = resources::getDaemonDirectory();
|
||||
if (!exe_dir.empty()) {
|
||||
std::error_code ec;
|
||||
fs::create_directories(daemon_dir, ec);
|
||||
|
||||
std::vector<std::string> searchDirs = { exe_dir };
|
||||
#ifdef __APPLE__
|
||||
{
|
||||
fs::path resourcesDir = fs::path(exe_dir).parent_path() / "Resources";
|
||||
if (fs::is_directory(resourcesDir))
|
||||
searchDirs.insert(searchDirs.begin(), resourcesDir.string());
|
||||
}
|
||||
#endif
|
||||
const char* extraFiles[] = { "asmap.dat", "dragonxd", "dragonx-cli", "dragonx-tx" };
|
||||
for (const char* name : extraFiles) {
|
||||
fs::path dst = fs::path(daemon_dir) / name;
|
||||
if (fs::exists(dst)) continue;
|
||||
for (const auto& dir : searchDirs) {
|
||||
fs::path src = fs::path(dir) / name;
|
||||
if (fs::exists(src)) {
|
||||
DEBUG_LOGF("Copying bundled %s from %s to %s\n", name, dir.c_str(), daemon_dir.c_str());
|
||||
fs::copy_file(src, dst, ec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create daemon manager if needed
|
||||
if (!embedded_daemon_) {
|
||||
embedded_daemon_ = std::make_unique<daemon::EmbeddedDaemon>();
|
||||
|
||||
@@ -696,6 +696,28 @@ void App::renderFirstRunWizard() {
|
||||
dimCol, "(wallet.dat is protected)");
|
||||
cy += captionFont->LegacySize + 6.0f * dp;
|
||||
}
|
||||
|
||||
// Daemon status indicator
|
||||
{
|
||||
bool daemonUp = isEmbeddedDaemonRunning();
|
||||
const std::string& dStatus = getDaemonStatus();
|
||||
ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200) // green
|
||||
: IM_COL32(120, 120, 120, 160); // gray
|
||||
if (dStatus.find("Stopping") != std::string::npos)
|
||||
dotCol = IM_COL32(255, 167, 38, 200); // orange
|
||||
float dotR = 3.5f * dp;
|
||||
dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
|
||||
dotR, dotCol);
|
||||
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
|
||||
? "Daemon stopping..."
|
||||
: "Daemon running")
|
||||
: "Daemon stopped";
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
|
||||
(dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
|
||||
cy += captionFont->LegacySize + 6.0f * dp;
|
||||
}
|
||||
|
||||
cy += 12.0f * dp;
|
||||
|
||||
// Cancel button
|
||||
@@ -895,6 +917,27 @@ void App::renderFirstRunWizard() {
|
||||
cy += twSize.y + 12.0f * dp;
|
||||
}
|
||||
|
||||
// Daemon status indicator (subtle, before buttons)
|
||||
{
|
||||
bool daemonUp = isEmbeddedDaemonRunning();
|
||||
const std::string& dStatus = getDaemonStatus();
|
||||
ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200)
|
||||
: IM_COL32(120, 120, 120, 160);
|
||||
if (dStatus.find("Stopping") != std::string::npos)
|
||||
dotCol = IM_COL32(255, 167, 38, 200);
|
||||
float dotR = 3.5f * dp;
|
||||
dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
|
||||
dotR, dotCol);
|
||||
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
|
||||
? "Daemon stopping..."
|
||||
: "Daemon running")
|
||||
: "Daemon stopped";
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
|
||||
(dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
|
||||
cy += captionFont->LegacySize + 10.0f * dp;
|
||||
}
|
||||
|
||||
// Buttons (only when focused)
|
||||
if (isFocused) {
|
||||
float dlBtnW = 150.0f * dp;
|
||||
|
||||
@@ -280,8 +280,13 @@ private:
|
||||
float blur_multiplier_ = 0.10f;
|
||||
float noise_opacity_ = 0.5f;
|
||||
bool gradient_background_ = false;
|
||||
#ifdef _WIN32
|
||||
float ui_opacity_ = 0.50f; // Card/sidebar opacity (0.3–1.0, 1.0 = opaque)
|
||||
float window_opacity_ = 0.75f; // Background alpha (0.3–1.0, <1 = desktop visible)
|
||||
#else
|
||||
float ui_opacity_ = 1.0f; // Mac/Linux: default fully opaque
|
||||
float window_opacity_ = 1.0f; // Mac/Linux: default fully opaque
|
||||
#endif
|
||||
std::string balance_layout_ = "classic";
|
||||
bool scanline_enabled_ = true;
|
||||
std::set<std::string> hidden_addresses_;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "embedded_daemon.h"
|
||||
#include "../config/version.h"
|
||||
#include "../resources/embedded_resources.h"
|
||||
#include "../util/platform.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -55,6 +56,8 @@ std::string EmbeddedDaemon::findDaemonBinary()
|
||||
char exe_path[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, exe_path, MAX_PATH);
|
||||
exe_dir = fs::path(exe_path).parent_path().string();
|
||||
#elif defined(__APPLE__)
|
||||
exe_dir = util::Platform::getExecutableDirectory();
|
||||
#else
|
||||
char exe_path[4096];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
@@ -349,25 +352,35 @@ static bool isPortInUse(int port)
|
||||
WSACleanup();
|
||||
return (result == 0);
|
||||
#else
|
||||
// Read /proc/net/tcp to check for listeners — avoids creating sockets
|
||||
// which would add conntrack entries and consume ephemeral ports.
|
||||
// On macOS /proc doesn't exist; on Linux prefer /proc/net/tcp to avoid
|
||||
// creating sockets. Fall back to connect() if /proc is unavailable.
|
||||
FILE* fp = fopen("/proc/net/tcp", "r");
|
||||
if (!fp) return false;
|
||||
char line[256];
|
||||
unsigned int localPort, state;
|
||||
bool found = false;
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
// Format: sl local_address rem_address st ...
|
||||
// local_address is HEXIP:HEXPORT, state 0x0A = TCP_LISTEN
|
||||
if (sscanf(line, " %*d: %*X:%X %*X:%*X %X", &localPort, &state) == 2) {
|
||||
if (localPort == static_cast<unsigned int>(port) && state == 0x0A) {
|
||||
found = true;
|
||||
break;
|
||||
if (fp) {
|
||||
char line[256];
|
||||
unsigned int localPort, state;
|
||||
bool found = false;
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (sscanf(line, " %*d: %*X:%X %*X:%*X %X", &localPort, &state) == 2) {
|
||||
if (localPort == static_cast<unsigned int>(port) && state == 0x0A) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return found;
|
||||
}
|
||||
fclose(fp);
|
||||
return found;
|
||||
// Fallback (macOS): try to connect
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) return false;
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(port));
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
|
||||
close(sock);
|
||||
return (result == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -925,7 +938,23 @@ double EmbeddedDaemon::getMemoryUsageMB() const
|
||||
{
|
||||
if (process_pid_ <= 0) return 0.0;
|
||||
|
||||
// The tracked PID is often a bash wrapper script; the real daemon
|
||||
#ifdef __APPLE__
|
||||
// macOS: use ps to read RSS for the daemon process and its children
|
||||
// in the same process group.
|
||||
char cmd[128];
|
||||
snprintf(cmd, sizeof(cmd), "ps -o rss= -g %d 2>/dev/null", process_pid_);
|
||||
FILE* fp = popen(cmd, "r");
|
||||
if (!fp) return 0.0;
|
||||
double total_rss_mb = 0.0;
|
||||
char line[64];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
long rss_kb = atol(line);
|
||||
if (rss_kb > 0) total_rss_mb += static_cast<double>(rss_kb) / 1024.0;
|
||||
}
|
||||
pclose(fp);
|
||||
return total_rss_mb;
|
||||
#else
|
||||
// Linux: The tracked PID is often a bash wrapper script; the real daemon
|
||||
// (dragonxd) is a child in the same process group. Sum VmRSS for every
|
||||
// process whose PGID matches our tracked PID.
|
||||
double total_rss_mb = 0.0;
|
||||
@@ -975,6 +1004,7 @@ double EmbeddedDaemon::getMemoryUsageMB() const
|
||||
}
|
||||
|
||||
return total_rss_mb;
|
||||
#endif // __APPLE__ / Linux
|
||||
}
|
||||
|
||||
bool EmbeddedDaemon::isRunning() const
|
||||
|
||||
@@ -512,6 +512,21 @@ bool XmrigManager::isRunning() const {
|
||||
|
||||
double XmrigManager::getMemoryUsageMB() const {
|
||||
if (process_pid_ <= 0) return 0.0;
|
||||
#ifdef __APPLE__
|
||||
// macOS: use ps to read RSS for xmrig process
|
||||
char cmd[128];
|
||||
snprintf(cmd, sizeof(cmd), "ps -o rss= -p %d 2>/dev/null", process_pid_);
|
||||
FILE* fp = popen(cmd, "r");
|
||||
if (!fp) return 0.0;
|
||||
char line[64];
|
||||
double mb = 0.0;
|
||||
if (fgets(line, sizeof(line), fp)) {
|
||||
long rss_kb = atol(line);
|
||||
if (rss_kb > 0) mb = static_cast<double>(rss_kb) / 1024.0;
|
||||
}
|
||||
pclose(fp);
|
||||
return mb;
|
||||
#else
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/statm", process_pid_);
|
||||
FILE* fp = fopen(path, "r");
|
||||
@@ -523,6 +538,7 @@ double XmrigManager::getMemoryUsageMB() const {
|
||||
fclose(fp);
|
||||
long pageSize = sysconf(_SC_PAGESIZE);
|
||||
return static_cast<double>(pages * pageSize) / (1024.0 * 1024.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void XmrigManager::drainOutput() {
|
||||
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
/// User-facing config (maps 1:1 to UI fields / Settings)
|
||||
struct Config {
|
||||
std::string pool_url = "pool.dragonx.is";
|
||||
std::string pool_url = "pool.dragonx.is:3433";
|
||||
std::string wallet_address;
|
||||
std::string worker_name = "x";
|
||||
std::string algo = "rx/hush";
|
||||
|
||||
49
src/main.cpp
49
src/main.cpp
@@ -663,12 +663,20 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
#else
|
||||
// OpenGL path (Linux)
|
||||
// OpenGL path (Linux / macOS)
|
||||
#ifdef __APPLE__
|
||||
// macOS requires GL 3.2 Core Profile + GLSL 150
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
#endif
|
||||
|
||||
// Create window with graphics context
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
@@ -795,6 +803,10 @@ int main(int argc, char* argv[])
|
||||
|
||||
// If we're starting on a HiDPI display, scale the window so the UI
|
||||
// appears the same physical size as on a 100% display.
|
||||
// On macOS with HIGH_PIXEL_DENSITY, window sizes are already in logical
|
||||
// (point) coordinates and the framebuffer handles pixel density, so
|
||||
// we must NOT multiply the window size by the content scale.
|
||||
#ifndef __APPLE__
|
||||
if (currentDpiScale > 1.01f) {
|
||||
int curW = 0, curH = 0;
|
||||
SDL_GetWindowSize(window, &curW, &curH);
|
||||
@@ -819,6 +831,7 @@ int main(int argc, char* argv[])
|
||||
DEBUG_LOGF("HiDPI startup: window resized %dx%d -> %dx%d (scale %.2f)\n",
|
||||
curW, curH, newW, newH, currentDpiScale);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create application instance
|
||||
dragonx::App app;
|
||||
@@ -1104,6 +1117,9 @@ int main(int argc, char* argv[])
|
||||
waitEvent.window.windowID == SDL_GetWindowID(window)) {
|
||||
float actualScale = SDL_GetWindowDisplayScale(window);
|
||||
float storedScale = dragonx::ui::material::Typography::instance().getDpiScale();
|
||||
#ifdef __APPLE__
|
||||
actualScale = storedScale; // macOS Retina: scales always match (both 1.0)
|
||||
#endif
|
||||
bool isDpiResize = dpiResizePending ||
|
||||
std::abs(actualScale - storedScale) > 0.01f;
|
||||
if (dpiResizePending) dpiResizePending = false;
|
||||
@@ -1124,6 +1140,9 @@ int main(int argc, char* argv[])
|
||||
waitEvent.window.windowID == SDL_GetWindowID(window)) {
|
||||
float newScale = SDL_GetWindowDisplayScale(window);
|
||||
if (newScale <= 0.0f) newScale = 1.0f;
|
||||
#ifdef __APPLE__
|
||||
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
|
||||
#endif
|
||||
auto& typo = dragonx::ui::material::Typography::instance();
|
||||
float oldScale = typo.getDpiScale();
|
||||
if (std::abs(newScale - oldScale) > 0.01f) {
|
||||
@@ -1266,6 +1285,9 @@ int main(int argc, char* argv[])
|
||||
event.window.windowID == SDL_GetWindowID(window)) {
|
||||
float actualScale = SDL_GetWindowDisplayScale(window);
|
||||
float storedScale = dragonx::ui::material::Typography::instance().getDpiScale();
|
||||
#ifdef __APPLE__
|
||||
actualScale = storedScale; // macOS Retina: scales always match (both 1.0)
|
||||
#endif
|
||||
bool isDpiResize = dpiResizePending ||
|
||||
std::abs(actualScale - storedScale) > 0.01f;
|
||||
if (dpiResizePending) dpiResizePending = false;
|
||||
@@ -1296,6 +1318,9 @@ int main(int argc, char* argv[])
|
||||
event.window.windowID == SDL_GetWindowID(window)) {
|
||||
float newScale = SDL_GetWindowDisplayScale(window);
|
||||
if (newScale <= 0.0f) newScale = 1.0f;
|
||||
#ifdef __APPLE__
|
||||
newScale = 1.0f; // macOS handles Retina via DisplayFramebufferScale
|
||||
#endif
|
||||
auto& typo = dragonx::ui::material::Typography::instance();
|
||||
float oldScale = typo.getDpiScale();
|
||||
if (std::abs(newScale - oldScale) > 0.01f) {
|
||||
@@ -1592,14 +1617,18 @@ int main(int argc, char* argv[])
|
||||
// inserted into the BackgroundDrawList above. It fires
|
||||
// during RenderDrawData() at exactly the right moment.
|
||||
|
||||
ImGuiErrorRecoveryState erState;
|
||||
ImGui::ErrorRecoveryStoreState(&erState);
|
||||
try {
|
||||
PERF_BEGIN(_perfRender);
|
||||
app.render();
|
||||
PERF_END("AppRender", _perfRender);
|
||||
} catch (const std::exception& e) {
|
||||
DEBUG_LOGF("[Main] app.render() threw: %s\n", e.what());
|
||||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||||
} catch (...) {
|
||||
DEBUG_LOGF("[Main] app.render() threw unknown exception\n");
|
||||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||||
}
|
||||
|
||||
// Draw shutdown overlay on top of everything when shutting down
|
||||
@@ -1802,10 +1831,12 @@ int main(int argc, char* argv[])
|
||||
|
||||
// Watchdog: if cleanup takes too long the process lingers without a
|
||||
// window, showing up as a "Background Service" in Task Manager.
|
||||
// Force-exit after 3 seconds — all critical state (settings, daemon
|
||||
// Force-exit after timeout — all critical state (settings, daemon
|
||||
// stop) was handled in beginShutdown().
|
||||
// Allow enough time for the daemon to shut down gracefully (up to
|
||||
// 10s in stop()) plus RPC disconnect overhead.
|
||||
std::thread([]() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(15));
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
_Exit(0);
|
||||
@@ -1932,11 +1963,23 @@ static bool InitImGui(SDL_Window* window, SDL_GLContext gl_context)
|
||||
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplSDL3_InitForOpenGL(window, gl_context);
|
||||
#ifdef __APPLE__
|
||||
ImGui_ImplOpenGL3_Init("#version 150");
|
||||
#else
|
||||
ImGui_ImplOpenGL3_Init("#version 130");
|
||||
#endif
|
||||
|
||||
// Query display DPI scale (e.g. 1.5 for 150% scaling)
|
||||
float dpiScale = SDL_GetWindowDisplayScale(window);
|
||||
if (dpiScale <= 0.0f) dpiScale = 1.0f;
|
||||
#ifdef __APPLE__
|
||||
// On macOS with SDL_WINDOW_HIGH_PIXEL_DENSITY, ImGui handles Retina
|
||||
// resolution automatically via io.DisplayFramebufferScale. Window
|
||||
// coordinates are already in logical points, so we must NOT also
|
||||
// scale fonts and style sizes by the content scale — that would
|
||||
// double everything.
|
||||
dpiScale = 1.0f;
|
||||
#endif
|
||||
DEBUG_LOGF("Display scale: %.2f\n", dpiScale);
|
||||
|
||||
// Load Material Design typography system with DPI awareness
|
||||
|
||||
@@ -110,7 +110,11 @@ struct ScrollFadeShader {
|
||||
#ifdef DRAGONX_HAS_GLAD
|
||||
|
||||
static constexpr const char* kFadeVS_130 =
|
||||
#ifdef __APPLE__
|
||||
"#version 150\n"
|
||||
#else
|
||||
"#version 130\n"
|
||||
#endif
|
||||
"uniform mat4 ProjMtx;\n"
|
||||
"in vec2 Position;\n"
|
||||
"in vec2 UV;\n"
|
||||
@@ -124,7 +128,11 @@ struct ScrollFadeShader {
|
||||
"}\n";
|
||||
|
||||
static constexpr const char* kFadeFS_130 =
|
||||
#ifdef __APPLE__
|
||||
"#version 150\n"
|
||||
#else
|
||||
"#version 130\n"
|
||||
#endif
|
||||
"uniform sampler2D Texture;\n"
|
||||
"uniform float u_fadeTopY;\n"
|
||||
"uniform float u_fadeBottomY;\n"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../notifications.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
@@ -104,19 +105,39 @@ static std::string FormatEstTime(double est_hours)
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void RenderMiningTabContent(App* app);
|
||||
|
||||
void RenderMiningTab(App* app)
|
||||
{
|
||||
// Scrollable child to contain all content within available space
|
||||
ImVec2 miningAvail = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("##MiningScroll", miningAvail, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
|
||||
|
||||
ImGuiErrorRecoveryState erState;
|
||||
ImGui::ErrorRecoveryStoreState(&erState);
|
||||
try {
|
||||
RenderMiningTabContent(app);
|
||||
} catch (const std::exception& e) {
|
||||
DEBUG_LOGF("[MiningTab] Exception: %s\n", e.what());
|
||||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||||
} catch (...) {
|
||||
DEBUG_LOGF("[MiningTab] Unknown exception\n");
|
||||
ImGui::ErrorRecoveryTryToRecoverState(&erState);
|
||||
}
|
||||
|
||||
ImGui::EndChild(); // ##MiningScroll
|
||||
}
|
||||
|
||||
static void RenderMiningTabContent(App* app)
|
||||
{
|
||||
auto& S = schema::UI();
|
||||
ImVec2 miningAvail = ImGui::GetContentRegionAvail();
|
||||
auto sliderInput = S.input("tabs.mining", "thread-slider");
|
||||
auto startBtn = S.button("tabs.mining", "start-button");
|
||||
auto lbl = S.label("tabs.mining", "label-column");
|
||||
const auto& state = app->getWalletState();
|
||||
const auto& mining = state.mining;
|
||||
|
||||
// Scrollable child to contain all content within available space
|
||||
ImVec2 miningAvail = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("##MiningScroll", miningAvail, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
|
||||
|
||||
// Responsive: scale factors per frame
|
||||
float availWidth = ImGui::GetContentRegionAvail().x;
|
||||
float hs = Layout::hScale(availWidth);
|
||||
@@ -155,6 +176,9 @@ void RenderMiningTab(App* app)
|
||||
ImFont* ovFont = Type().overline();
|
||||
ImFont* capFont = Type().caption();
|
||||
ImFont* sub1 = Type().subtitle1();
|
||||
if (!ovFont || !capFont || !sub1) {
|
||||
return;
|
||||
}
|
||||
char buf[128];
|
||||
|
||||
// Load pool state from settings on first frame
|
||||
@@ -750,7 +774,7 @@ void RenderMiningTab(App* app)
|
||||
OnSurfaceMedium(), resetIcon);
|
||||
|
||||
if (btnClk) {
|
||||
strncpy(s_pool_url, "pool.dragonx.is", sizeof(s_pool_url) - 1);
|
||||
strncpy(s_pool_url, "pool.dragonx.is:3433", sizeof(s_pool_url) - 1);
|
||||
// Default to user's first shielded (z) address for pool payouts.
|
||||
// Leave blank if no z-address exists yet.
|
||||
std::string defaultAddr;
|
||||
@@ -1977,11 +2001,13 @@ void RenderMiningTab(App* app)
|
||||
if (!mining_address.empty()) {
|
||||
float addrAvailW = colW - pad - valOffX;
|
||||
float charW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "M").x;
|
||||
if (charW <= 0.0f) charW = 8.0f;
|
||||
int maxChars = std::max(8, (int)(addrAvailW / charW));
|
||||
std::string truncAddr = mining_address;
|
||||
if ((int)truncAddr.length() > maxChars) {
|
||||
if ((int)truncAddr.length() > maxChars && maxChars > 5) {
|
||||
int half = (maxChars - 3) / 2;
|
||||
truncAddr = truncAddr.substr(0, half) + "..." + truncAddr.substr(truncAddr.length() - half);
|
||||
if (half > 0 && (size_t)half < truncAddr.length())
|
||||
truncAddr = truncAddr.substr(0, half) + "..." + truncAddr.substr(truncAddr.length() - half);
|
||||
}
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurface(), truncAddr.c_str());
|
||||
|
||||
@@ -2342,8 +2368,6 @@ void RenderMiningTab(App* app)
|
||||
OnSurfaceMedium(), statusText);
|
||||
ImGui::Dummy(ImVec2(availWidth, capFont->LegacySize + 4 * dp));
|
||||
}
|
||||
|
||||
ImGui::EndChild(); // ##MiningScroll
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -150,6 +150,8 @@ std::string Platform::getDragonXDataDir()
|
||||
return std::string(path) + "\\Hush\\DRAGONX\\";
|
||||
}
|
||||
return getHomeDir() + "\\AppData\\Roaming\\Hush\\DRAGONX\\";
|
||||
#elif defined(__APPLE__)
|
||||
return getHomeDir() + "/Library/Application Support/Hush/DRAGONX/";
|
||||
#else
|
||||
return getHomeDir() + "/.hush/DRAGONX/";
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user