Files
Toolbox/src/toolboxWindow.cpp
Yadciel 843f978309 Fügt grundlegende Toolbox-HTML-Struktur hinzu
Erstellt die anfängliche HTML-Struktur für die Toolbox-Oberfläche.

Dies beinhaltet das Hinzufügen von Head-, Header-, Main- und Footer-Bereichen,
sowie grundlegende Button-Funktionalitäten und dynamisches Laden von Tool-Karten.

Ebenfalls enthalten sind CSS-Styles, um das Aussehen zu gestalten und ein Stylesheet ist eingebettet.
2026-02-02 21:55:20 +01:00

765 lines
24 KiB
C++

#include "toolboxWindow.h"
#include <iostream>
#include <stdexcept>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <algorithm>
#include <nlohmann/json.hpp>
#include <cppcodec/base64_rfc4648.hpp>
#include <saucer/embedded/all.hpp>
#include <saucer/navigation.hpp>
#include <saucer/app.hpp>
#include <format>
#include <chrono>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
}
#include <fstream>
#include <sstream>
#include <unordered_map>
namespace {
std::unordered_map<HWND, WNDPROC> g_toolbox_prev_procs;
LRESULT CALLBACK toolbox_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (msg == WM_KEYDOWN && wparam == VK_F12) {
auto *self = reinterpret_cast<ToolboxWindow *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (self) {
self->toggleDevTools();
return 0;
}
}
const auto it = g_toolbox_prev_procs.find(hwnd);
if (it != g_toolbox_prev_procs.end()) {
return CallWindowProc(it->second, hwnd, msg, wparam, lparam);
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
void install_toolbox_f12_toggle(HWND hwnd, ToolboxWindow *self) {
if (!hwnd || !self) {
return;
}
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self));
auto prev = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(toolbox_wndproc))
);
if (prev) {
g_toolbox_prev_procs[hwnd] = prev;
}
}
}
ToolboxWindow::ToolboxWindow(std::shared_ptr<saucer::application>& app)
: m_webview([&]() -> saucer::smartview {
auto window_res = saucer::window::create(app.get());
if (!window_res.has_value()) {
throw std::runtime_error("ToolboxWindow: failed to create saucer::window");
}
saucer::webview::options opts{window_res.value()};
opts.browser_flags.insert("--disable-gpu");
auto sv_res = saucer::smartview::create(opts);
if (!sv_res.has_value()) {
throw std::runtime_error("ToolboxWindow: failed to create saucer::smartview");
}
return std::move(sv_res.value());
}())
, m_app(app)
{}
void ToolboxWindow::initialize() {
m_hwnd_module = std::make_unique<hwnd_module>(&m_webview);
m_hwnd = m_hwnd_module->get_hwnd();
install_toolbox_f12_toggle(m_hwnd, this);
ensureToolboxInUserPath();
fillToolboxMenu();
configureWindow();
loadResources();
setupToolboxWindow();
}
void ToolboxWindow::setupToolboxWindow() {
SetJsonItems();
ExposeToJs();
m_webview.on<saucer::webview::event::navigate>([](const saucer::navigation& nav) {
const auto scheme = nav.url().scheme();
const auto host = nav.url().host();
if (scheme == "saucer" && host && *host == "embedded") {
return saucer::policy::allow;
}
if (scheme == "about") {
return saucer::policy::allow;
}
return saucer::policy::block;
});
m_webview.parent().set_always_on_top(true);
m_webview.parent().set_decorations(saucer::window::decoration::none);
#ifdef _DEBUG
m_webview.set_dev_tools(true);
#endif
startThreadMonitor();
}
void ToolboxWindow::startThreadMonitor() {
m_threadMonitor = std::thread([&]() {
monitor_focus();
});
m_threadMonitor.detach();
}
bool ToolboxWindow::serveHtmlFile(const std::string& htmlPath, bool setDecorations) {
auto resources = saucer::embedded::all();
const std::string key = (!htmlPath.empty() && htmlPath[0] != '/') ? "/" + htmlPath : htmlPath;
if (!resources.contains(key)) {
std::cerr << "Fehler: '" << key << "' wurde nicht gefunden!" << std::endl;
return false;
}
m_webview.serve(key);
m_webview.parent().set_decorations(setDecorations ? saucer::window::decoration::full : saucer::window::decoration::none);
return true;
}
void ToolboxWindow::configureWindow() {
// DPI-Awareness berücksichtigen
SetProcessDPIAware(); // Macht die Anwendung DPI-bewusst
// Aktuelle DPI-Einstellungen ermitteln
HDC hdc = GetDC(NULL);
int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
// Skalierungsfaktor berechnen (96 DPI = 100% Skalierung)
const double scaleX = static_cast<double>(dpiX) / 96.0;
const double scaleY = static_cast<double>(dpiY) / 96.0;
// Physische Bildschirmauflösung
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// Logische Auflösung (was der Benutzer sieht)
const int logicalScreenWidth = static_cast<int>(screenWidth / scaleX);
const int logicalScreenHeight = static_cast<int>(screenHeight / scaleY);
const int windowWidth = 420;
const int windowHeight = 720;
m_webview.parent().set_size({windowWidth, windowHeight});
const saucer::color bg{0x0b, 0x1c, 0x2c, 0x00};
m_webview.set_background(bg);
m_webview.parent().set_background(bg);
// Arbeitsbereich mit DPI-Skalierung berücksichtigen
RECT workArea;
SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
// Arbeitsbereich in logische Koordinaten umrechnen
const int logicalWorkWidth = static_cast<int>((workArea.right - workArea.left) / scaleX);
const int logicalWorkHeight = static_cast<int>((workArea.bottom - workArea.top) / scaleY);
const int rightMargin = 0;
const int bottomMargin = 0;
// Position in logischen Koordinaten berechnen
int xPos = logicalWorkWidth - rightMargin - windowWidth;
int yPos = logicalWorkHeight - bottomMargin - windowHeight;
// Boundary checks
if (xPos < 0) xPos = 10;
if (yPos < 0) yPos = 10;
// Zurück in physische Koordinaten für SetWindowPos
const int physicalX = static_cast<int>(xPos * scaleX);
const int physicalY = static_cast<int>(yPos * scaleY);
moveWindowTo(physicalX, physicalY);
}
void ToolboxWindow::moveWindowTo(int x, int y) {
if (const HWND hwnd = m_hwnd) {
SetWindowPos(hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}
}
void ToolboxWindow::loadResources() {
auto resources = saucer::embedded::all();
if (!resources.contains("/html/card.html")) {
std::cerr << "Fehler: 'card.html' wurde nicht gefunden!" << std::endl;
return;
}
if (const auto it = resources.find("/html/index.html"); it == resources.end()) {
std::cerr << "Fehler: 'index.html' wurde nicht gefunden!" << std::endl;
return;
}
m_webview.embed(resources);
m_webview.set_url("saucer://embedded/html/index.html");
//m_webview.set_url("https://nindo.de");
m_webview.set_context_menu(false);
if (!resources.contains("ico/toolbox.png")) {
std::cerr << "Fehler: 'toolbox.png' wurde nicht gefunden!" << std::endl;
return;
}
const saucer::icon window_icon = saucer::icon::from(resources.find("ico/toolbox.png")->second.content).value();
m_webview.parent().set_icon(window_icon);
m_webview.parent().set_title("Toolbox");
saucer::webview::register_scheme("Toolbox");
}
void ToolboxWindow::runLuaScript() {
sol::state lua;
lua.open_libraries(sol::lib::base);
lua.script("print('Hallo von Lua!')");
}
void ToolboxWindow::show() {
m_webview.parent().show();
m_bShow = true;
m_lastShowTime = std::chrono::steady_clock::now();
m_seenFocusSinceShow = false;
// In some cases the HWND/frame metrics are finalized only after showing.
// Re-acquire HWND and reposition now, plus schedule a short delayed reposition.
if (m_hwnd_module) {
if (HWND newHwnd = m_hwnd_module->get_hwnd(); newHwnd) {
m_hwnd = newHwnd;
}
}
if (m_hwnd) {
SetForegroundWindow(m_hwnd);
SetActiveWindow(m_hwnd);
SetFocus(m_hwnd);
}
// Immediate reposition after showing
configureWindow();
// Deferred reposition to catch late layouting from the framework
std::thread([this]() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
configureWindow();
}).detach();
}
void ToolboxWindow::hide() {
m_webview.parent().hide();
m_bShow = false;
}
void ToolboxWindow::ExposeToJs() {
// Funktion, die JSON-Code an das JavaScript exponiert
m_webview.expose("getToolboxItems", [&]() -> std::string {
return jsonString;
});
m_webview.expose("run_lua", [&](int id) {
run_lua(id);
});
m_webview.expose("cancel_lua", [&](int id) {
cancel_lua(id);
});
m_webview.expose("debug_print", [&]() {
debug_print();
});
m_webview.expose("open_file_system", [&]() {
open_file_system();
});
m_webview.expose("openEinst", [&](int id) {
openEinst(id);
});
m_webview.expose("openIndex", [&]() {
openIndex();
});
m_webview.expose("getNewContent", [&]()
{
getNewContent();
});
m_webview.expose("getLuaTimeoutSeconds", [&]() -> int {
return getLuaTimeoutSeconds();
});
m_webview.expose("setLuaTimeoutSeconds", [&](int seconds) {
setLuaTimeoutSeconds(seconds);
});
m_webview.expose("getActiveToolName", [&]() -> std::string {
return getActiveToolName();
});
m_webview.expose("getToolConfigIni", [&]() -> std::string {
return getToolConfigIni();
});
m_webview.expose("setToolConfigIni", [&](const std::string &content) {
setToolConfigIni(content);
});
}
void ToolboxWindow::SetJsonItems() {// Konvertierung des Vektors in JSON
nlohmann::json jsonArray = nlohmann::json::array();
for (const auto& item : m_toolboxItems) {
nlohmann::json jsonItem = {
{"id", item.id},
{"name", item.name},
{"path", item.path},
{"icon", item.icon},
{"description", item.description},
{"category", item.category},
{"version", item.version},
{"author", item.author},
{"url", item.url},
{"license", item.license}
};
jsonArray.push_back(jsonItem);
}
jsonString = "";
// Konvertiere den JSON-Array in einen String
jsonString = jsonArray.dump();
}
bool ToolboxWindow::ensureToolboxInUserPath() {
// Bestimme das Benutzerverzeichnis abhängig vom Betriebssystem
std::filesystem::path userPath;
#ifdef _WIN32
userPath = std::getenv("USERPROFILE"); // Windows
#else
userPath = std::getenv("HOME"); // Unix/Linux/MacOS
#endif
// Setze den Pfad zum .Toolbox-Verzeichnis
// Überprüfe, ob der Pfad existiert
if (const std::filesystem::path toolboxPath = userPath / ".Toolbox"; !std::filesystem::exists(toolboxPath)) {
try {
// Versuche, das Verzeichnis zu erstellen
if (std::filesystem::create_directory(toolboxPath)) {
std::cout << ".Toolbox Verzeichnis wurde erstellt." << std::endl;
return true;
} else {
std::cerr << "Fehler beim Erstellen des .Toolbox Verzeichnisses." << std::endl;
return false;
}
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Filesystem-Fehler: " << e.what() << std::endl;
return false;
}
}
std::cout << "Das Verzeichnis .Toolbox existiert bereits." << std::endl;
return true;
}
void encodeImageToBase64(const std::filesystem::path& imagePath, ToolboxWindow::ToolboxItem& item) {
std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Fehler beim Öffnen des PNG-Bildes: " << imagePath << std::endl;
return;
}
const std::streamsize fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> fileData(fileSize);
if (!file.read(reinterpret_cast<char*>(fileData.data()), fileSize)) {
std::cerr << "Fehler beim Lesen des PNG-Bildes: " << imagePath << std::endl;
return;
}
// Kodieren der Binärdaten mit cppcodec zu Base64
const std::string base64Encoded = cppcodec::base64_rfc4648::encode(fileData);
item.icon = "data:image/png;base64," + base64Encoded;
}
void ToolboxWindow::fillToolboxMenu() {
m_toolboxItems.clear();
std::filesystem::path toolboxDir = userPath / ".Toolbox";
if (!std::filesystem::exists(toolboxDir) || !std::filesystem::is_directory(toolboxDir)) {
std::cerr << "Fehler: Das Verzeichnis .Toolbox existiert nicht." << std::endl;
return;
}
int id = 0;
for (const auto& entry : std::filesystem::directory_iterator(toolboxDir)) {
if (!entry.is_directory()) continue;
ToolboxItem item;
item.id = std::to_string(id++);
item.name = entry.path().filename().string();
item.path = entry.path().string();
// Encode Image
encodeImageToBase64(entry.path() / "icon.png", item);
// Lese Beschreibung von description.txt
std::filesystem::path descriptionFile = entry.path() / "description.txt";
if (std::filesystem::exists(descriptionFile)) {
std::ifstream descFile(descriptionFile);
item.description.assign((std::istreambuf_iterator<char>(descFile)),
std::istreambuf_iterator<char>());
}
// Lese Beschreibung von description.txt
std::filesystem::path luaScript = entry.path() / "start.lua";
if (std::filesystem::exists(luaScript)) {
item.lua_script_path_string = luaScript.string();
}
m_toolboxItems.push_back(item);
}
}
void ToolboxWindow::run_lua(int id) {
/*// Öffnet ein CMD-Fenster, falls noch keins offen ist
static bool console_opened = false;
if (!console_opened) {
AllocConsole();
freopen("CONOUT$", "w", stdout); // stdout auf die Konsole umleiten
freopen("CONOUT$", "w", stderr); // stderr auf die Konsole umleiten
console_opened = true;
}*/
constexpr int kHookInstructionCount = 10000;
const int timeoutSeconds = getLuaTimeoutSecondsForId(id);
const auto deadline = (timeoutSeconds > 0)
? (std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSeconds))
: (std::chrono::steady_clock::time_point::max)();
auto cancelFlag = std::make_shared<std::atomic_bool>(false);
{
std::lock_guard<std::mutex> lock(m_luaTasksMutex);
m_luaTasks[id] = LuaTask{cancelFlag};
}
// Lambda-Funktion, um das Lua-Skript auszuführen
auto lua_task = [this, id, cancelFlag, deadline]() {
sol::state lua;
lua.open_libraries(sol::lib::base,
sol::lib::os,
sol::lib::string,
sol::lib::coroutine,
sol::lib::package,
sol::lib::table,
sol::lib::math,
sol::lib::utf8,
sol::lib::io);
struct LuaHookContext {
std::shared_ptr<std::atomic_bool> cancel;
std::chrono::steady_clock::time_point deadline;
};
LuaHookContext ctx{
cancelFlag,
deadline
};
lua_State* L = lua.lua_state();
*reinterpret_cast<LuaHookContext**>(lua_getextraspace(L)) = &ctx;
lua_sethook(
L,
[](lua_State* hookState, lua_Debug*) {
auto* hookCtx = *reinterpret_cast<LuaHookContext**>(lua_getextraspace(hookState));
if (!hookCtx) {
return;
}
if (hookCtx->cancel->load(std::memory_order_relaxed)) {
luaL_error(hookState, "Lua script cancelled");
}
if (hookCtx->deadline != (std::chrono::steady_clock::time_point::max)() &&
std::chrono::steady_clock::now() > hookCtx->deadline) {
luaL_error(hookState, "Lua script cancelled or timed out");
}
},
LUA_MASKCOUNT,
kHookInstructionCount
);
try {
std::string scriptPath = m_toolboxItems[id].lua_script_path_string;
std::cout << "Running Lua script: " << scriptPath << std::endl;
// Lua-Skript ausführen
lua.script_file(scriptPath);
// Nach erfolgreichem Ausführen UI aktualisieren
m_webview.saucer::webview::execute(std::format("enableButtonByToolId({})", id));
m_webview.reload();
} catch (const sol::error& e) {
// Lua-Fehler in der Konsole ausgeben
std::cerr << "Lua error: " << e.what() << std::endl;
// Nach Fehler UI aktualisieren
m_webview.saucer::webview::execute(std::format("enableButtonByToolId({})", id));
m_webview.reload();
}
{
std::lock_guard<std::mutex> lock(m_luaTasksMutex);
m_luaTasks.erase(id);
}
};
// Erstellen und starten des Threads
std::thread lua_thread(lua_task);
// Thread lösen, um die Anwendung nicht zu blockieren
lua_thread.detach();
std::cout << "Lua-Skript wurde gestartet." << std::endl;
}
void ToolboxWindow::cancel_lua(int id) {
std::lock_guard<std::mutex> lock(m_luaTasksMutex);
auto it = m_luaTasks.find(id);
if (it != m_luaTasks.end() && it->second.cancel) {
it->second.cancel->store(true, std::memory_order_relaxed);
}
}
void ToolboxWindow::debug_print() {
std::cout << "ToolboxWindow::debug_print()" << std::endl;
}
ToolboxWindow::~ToolboxWindow() {
m_runningMonitor = false;
}
void ToolboxWindow::monitor_focus() {
while (m_runningMonitor) {
if (m_bShow && isIndex) {
const bool focused = m_webview.parent().focused();
if (focused) {
m_seenFocusSinceShow = true;
} else if (m_seenFocusSinceShow) {
this->hide();
}
}
// Schlafpause hinzufügen, um die CPU-Last zu reduzieren
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void ToolboxWindow::open_file_system() {
if (const std::filesystem::path toolboxDir = userPath / ".Toolbox"; std::filesystem::exists(toolboxDir) && std::filesystem::is_directory(toolboxDir)) {
std::string path = toolboxDir.string();
std::string command;
#ifdef _WIN32
command = "explorer \"" + path + "\"";
#elif __APPLE__
command = "open \"" + path + "\"";
#elif __linux__
command = "xdg-open \"" + path + "\"";
#else
std::cerr << "Fehler: Plattform nicht unterstützt." << std::endl;
return;
#endif
if (const int result = std::system(command.c_str()); result != 0) {
std::cerr << "Fehler beim Öffnen des Verzeichnisses: " << result << std::endl;
}
} else {
std::cerr << "Fehler: Das Verzeichnis .Toolbox existiert nicht." << std::endl;
}
fillToolboxMenu();
SetJsonItems();
m_webview.saucer::webview::execute("reloadContent()");
}
// Beispielhafte Nutzung:
void ToolboxWindow::openEinst() {
if (!m_optionWindow) {
m_optionWindow = std::make_unique<optionWindow>(
m_app,
[this]() { return getLuaTimeoutSeconds(); },
[this](int seconds) { setLuaTimeoutSeconds(seconds); },
[this]() { return getActiveToolName(); },
[this]() { return getToolConfigIni(); },
[this](const std::string &content) { setToolConfigIni(content); },
[this]() { openIndex(); }
);
m_optionWindow->initialize();
}
m_optionWindow->show();
}
void ToolboxWindow::openEinst(int id) {
if (id < 0 || id >= static_cast<int>(m_toolboxItems.size())) {
std::cerr << "openEinst: invalid tool id " << id << std::endl;
return;
}
m_activeToolId = id;
ensureConfigExists(id);
openEinst();
}
void ToolboxWindow::centerWindow() {
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// Aktuelle Fenstergröße abrufen
int windowWidth = 0;
int windowHeight = 0;
if (const HWND hwnd = m_hwnd) {
RECT rect;
if (GetWindowRect(hwnd, &rect)) {
windowWidth = rect.right - rect.left;
windowHeight = rect.bottom - rect.top;
}
}
// Berechnung der zentralen Position
const int xPos = (screenWidth - windowWidth) / 2;
const int yPos = (screenHeight - windowHeight) / 2;
// Bewege das Fenster an die zentrale Position
moveWindowTo(xPos, yPos);
}
void ToolboxWindow::resizeWindow(int width, int height)
{
// Stellen Sie die Größe des Fensters ein
m_webview.parent().set_size({width, height});
}
void ToolboxWindow::resizeWindow(double widthPercent, double heightPercent)
{
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// Berechnung der Fenstergröße basierend auf Prozentwerten
const int width = static_cast<int>(screenWidth * (widthPercent / 100.0));
const int height = static_cast<int>(screenHeight * (heightPercent / 100.0));
// Stellen Sie die Größe des Fensters ein
m_webview.parent().set_size({width, height});
}
void ToolboxWindow::openIndex() {
if (!m_bShow) {
show();
}
if (!serveHtmlFile("html/index.html", false)) {
return;
}
getNewContent();
}
void ToolboxWindow::getNewContent()
{
configureWindow();
fillToolboxMenu();
SetJsonItems();
m_webview.saucer::webview::execute("reloadContent()");
isIndex = true;
}
void ToolboxWindow::toggleDevTools() {
m_webview.set_dev_tools(!m_webview.dev_tools());
}
std::filesystem::path ToolboxWindow::getToolConfigPath(int id) const {
if (id < 0 || id >= static_cast<int>(m_toolboxItems.size())) {
return {};
}
return std::filesystem::path(m_toolboxItems[id].path) / "config.ini";
}
void ToolboxWindow::ensureConfigExists(int id) const {
const auto configPath = getToolConfigPath(id);
if (configPath.empty()) {
return;
}
m_configStore.ensureExists(configPath);
}
int ToolboxWindow::getLuaTimeoutSecondsForId(int id) const {
const auto configPath = getToolConfigPath(id);
if (configPath.empty()) {
return 30;
}
return m_configStore.getInt(configPath, "lua_timeout_seconds", 30);
}
int ToolboxWindow::getLuaTimeoutSeconds() {
if (m_activeToolId < 0) {
return 30;
}
return getLuaTimeoutSecondsForId(m_activeToolId);
}
void ToolboxWindow::setLuaTimeoutSeconds(int seconds) {
if (m_activeToolId < 0) {
return;
}
const auto configPath = getToolConfigPath(m_activeToolId);
if (configPath.empty()) {
return;
}
m_configStore.setInt(configPath, "lua_timeout_seconds", seconds);
}
std::string ToolboxWindow::getToolConfigIni() const {
if (m_activeToolId < 0) {
return {};
}
const auto configPath = getToolConfigPath(m_activeToolId);
if (configPath.empty()) {
return {};
}
return m_configStore.readAll(configPath);
}
void ToolboxWindow::setToolConfigIni(const std::string &content) {
if (m_activeToolId < 0) {
return;
}
const auto configPath = getToolConfigPath(m_activeToolId);
if (configPath.empty()) {
return;
}
m_configStore.writeAll(configPath, content);
}
std::string ToolboxWindow::getActiveToolName() {
if (m_activeToolId < 0 || m_activeToolId >= static_cast<int>(m_toolboxItems.size())) {
return {};
}
return m_toolboxItems[m_activeToolId].name;
}