Refakturiert die Anwendung und modernisiert das Build-System
Diese umfangreiche Überarbeitung verbessert die Architektur, den Build-Prozess und die Benutzeroberfläche der Toolbox. Wesentliche Änderungen: - **Build-System:** Umfassende Modernisierung des CMake-Builds für verbesserte Stabilität und Wartbarkeit. Die Konfiguration wurde auf zielbasierte Befehle umgestellt, `c_tray` stabilisiert und `saucer-desktop` entfernt. - **Modulare Architektur:** Die Kernlogik von `ToolboxWindow` wurde in dedizierte Helferklassen ausgelagert, darunter `LuaRunner` für die Skriptausführung, `ToolRegistry` für die Tool-Erkennung, `ResourceLoader` für UI-Ressourcen, `ToolboxJsBridge` für JavaScript-Bindungen und `WindowPlacement` für die Fensterverwaltung. Dies reduziert die Komplexität und fördert die Wiederverwendbarkeit. - **Benutzeroberfläche (UI):** Die HTML-Templates (`einstellungen_custom.html`, `index.html`) und Stylesheets (`styles.css`) wurden zentralisiert und überarbeitet, um eine kohärentere Benutzeroberfläche mit benutzerdefinierten Fensterdekorationen zu bieten. Neue Funktionen wie das Öffnen der Kommandozeile und eine "Tool hinzufügen"-Karte wurden integriert. - **Tray-Verwaltung:** Die Tray-Icon-Logik wurde in `TrayController` gekapselt, um eine robustere und zentralisierte Steuerung zu ermöglichen, insbesondere im Umgang mit Explorer-Neustarts.
This commit is contained in:
121
CMakeLists.txt
121
CMakeLists.txt
@@ -1,11 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.30)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(saucer_no_version_check ON)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
option(BUILD_SHARED_LIBS "Build as shared library" OFF)
|
||||
project(Toolbox LANGUAGES C CXX)
|
||||
|
||||
project(Toolbox)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE)
|
||||
set(saucer_no_version_check ON CACHE BOOL "Skip saucer version checks" FORCE)
|
||||
|
||||
# MSVC 19.44 provides the STL features saucer-fill may try to polyfill
|
||||
# (jthread, stop_token, move_only_function, join_with). Keep the polyfill target
|
||||
# present for saucer, but disable replacements to avoid duplicate declarations.
|
||||
set(fill_replacements "" CACHE STRING "saucer-fill replacements" FORCE)
|
||||
|
||||
add_subdirectory(lib/c_tray)
|
||||
add_subdirectory(lib/json)
|
||||
@@ -13,88 +20,94 @@ add_subdirectory(lib/cppcodec)
|
||||
add_subdirectory(lib/saucer)
|
||||
add_subdirectory(lib/lua)
|
||||
add_subdirectory(lib/sol2)
|
||||
#add_subdirectory(lib/saucer4lua)
|
||||
add_subdirectory(lib/toolbox_win)
|
||||
# add_subdirectory(lib/saucer4lua)
|
||||
|
||||
include_directories(inc)
|
||||
include_directories(lib/json/include/nlohmann)
|
||||
#include_directories(out_embed)
|
||||
file(GLOB_RECURSE APP_SOURCES CONFIGURE_DEPENDS
|
||||
"src/*.cpp"
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE APP_HEADERS CONFIGURE_DEPENDS
|
||||
"inc/*.h"
|
||||
"inc/*.hpp"
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
set(RESOURCE_FILES toolbox.rc)
|
||||
list(APPEND APP_SOURCES toolbox.rc)
|
||||
endif ()
|
||||
|
||||
# Header-Datei einbinden
|
||||
include_directories(lib/c_tray)
|
||||
|
||||
# Plattformabhängige Einstellungen
|
||||
if (WIN32)
|
||||
set(PLATFORM_SOURCES lib/c_tray/tray_windows.c)
|
||||
set(LIBS shell32)
|
||||
elseif (APPLE)
|
||||
set(PLATFORM_SOURCES lib/c_tray/tray_darwin.m)
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
set(LIBS ${COCOA_LIBRARY})
|
||||
elseif (UNIX)
|
||||
set(PLATFORM_SOURCES lib/c_tray/tray_linux.c)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(APPINDICATOR REQUIRED appindicator3-0.1)
|
||||
include_directories(${APPINDICATOR_INCLUDE_DIRS})
|
||||
set(LIBS ${APPINDICATOR_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp" "inc/*.h")
|
||||
list(APPEND SOURCES "src/toolConfig.cpp") # Nur für .cpp und .h
|
||||
file(GLOB_RECURSE MODULE_SOURCES "module/*.ixx") # Separates GLOB für Module
|
||||
|
||||
add_executable(Toolbox main.cpp
|
||||
${SOURCES}
|
||||
${RESOURCE_FILES}
|
||||
${PLATFORM_SOURCES}
|
||||
add_executable(Toolbox
|
||||
main.cpp
|
||||
${APP_SOURCES}
|
||||
${APP_HEADERS}
|
||||
)
|
||||
|
||||
target_sources(Toolbox
|
||||
PUBLIC
|
||||
PUBLIC
|
||||
FILE_SET cxx_modules TYPE CXX_MODULES
|
||||
FILES ${MODULE_SOURCES}
|
||||
FILES
|
||||
module/toolboxTray.ixx
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "out_embed")
|
||||
target_include_directories(Toolbox
|
||||
PRIVATE
|
||||
inc
|
||||
lib/c_tray
|
||||
)
|
||||
|
||||
# Ensure WebView2 headers from saucer's NuGet are available to this target (for saucer webview2 backend)
|
||||
if (WIN32)
|
||||
# Find the WebView2 include folder that saucer fetched into the build tree
|
||||
target_compile_definitions(Toolbox PRIVATE NOMINMAX UNICODE _UNICODE)
|
||||
|
||||
file(GLOB WEBVIEW2_INCLUDE_DIR
|
||||
"${CMAKE_BINARY_DIR}/lib/saucer/nuget/packages/Microsoft.Web.WebView2.*/build/native/include")
|
||||
"${CMAKE_BINARY_DIR}/lib/saucer/nuget/packages/Microsoft.Web.WebView2.*/build/native/include"
|
||||
)
|
||||
|
||||
if (WEBVIEW2_INCLUDE_DIR)
|
||||
message(STATUS "Using WebView2 includes at: ${WEBVIEW2_INCLUDE_DIR}")
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${WEBVIEW2_INCLUDE_DIR})
|
||||
target_include_directories(Toolbox PRIVATE ${WEBVIEW2_INCLUDE_DIR})
|
||||
else ()
|
||||
message(WARNING "WebView2 headers not found in build tree. The build may fail when including WebView2.h.")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
saucer_embed("res" TARGET ${PROJECT_NAME})
|
||||
set(EMBED_DESTINATION "${CMAKE_BINARY_DIR}/embedded")
|
||||
file(MAKE_DIRECTORY "${EMBED_DESTINATION}/saucer/embedded")
|
||||
|
||||
CPMFindPackage(
|
||||
NAME saucer-desktop
|
||||
VERSION 4.0.2
|
||||
GIT_REPOSITORY "https://github.com/saucer/desktop"
|
||||
file(GLOB_RECURSE EMBED_RESOURCE_FILES CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/res/*"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
foreach (RESOURCE_FILE IN LISTS EMBED_RESOURCE_FILES)
|
||||
if (IS_DIRECTORY "${RESOURCE_FILE}")
|
||||
continue()
|
||||
endif ()
|
||||
|
||||
file(RELATIVE_PATH RESOURCE_RELATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/res" "${RESOURCE_FILE}")
|
||||
get_filename_component(RESOURCE_RELATIVE_DIR "${RESOURCE_RELATIVE_PATH}" DIRECTORY)
|
||||
file(MAKE_DIRECTORY "${EMBED_DESTINATION}/saucer/embedded/files/${RESOURCE_RELATIVE_DIR}")
|
||||
endforeach ()
|
||||
|
||||
saucer_embed("res"
|
||||
TARGET Toolbox
|
||||
DESTINATION "${EMBED_DESTINATION}"
|
||||
)
|
||||
|
||||
target_link_libraries(Toolbox
|
||||
PRIVATE
|
||||
saucer::saucer
|
||||
sol2::sol2
|
||||
lua::lib
|
||||
tray
|
||||
nlohmann_json
|
||||
cppcodec
|
||||
#saucer4lua
|
||||
toolbox_win
|
||||
saucer::embedded
|
||||
saucer::desktop
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(Toolbox PRIVATE shell32 user32 gdi32)
|
||||
endif ()
|
||||
|
||||
if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel"))
|
||||
set_target_properties(Toolbox PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif ()
|
||||
|
||||
81
REPO_REVIEW_PLAN.md
Normal file
81
REPO_REVIEW_PLAN.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Repository Review Plan
|
||||
|
||||
## Kurzbewertung
|
||||
|
||||
Das Projekt ist ein brauchbarer Prototyp fuer eine Windows-Tray-Toolbox mit Saucer/WebView-UI, lokalen `.Toolbox`-Tools, Lua-Ausfuehrung und Tool-Konfiguration. Die Produktidee ist klar, aber die technische Struktur ist noch stark gewachsen: viel Verantwortung sitzt in `ToolboxWindow`, Threading ist riskant, der Build ist fragil und die HTML-Bruecke braucht Haertung.
|
||||
|
||||
## Priorisierter Plan
|
||||
|
||||
### 1. Build stabilisieren
|
||||
|
||||
Status: teilweise erledigt. Der lokale Debug-Build, CLion-CMake-Reload und ein Runtime-Smoke-Test von `Toolbox.exe` sind auf diesem Arbeitsplatz stabil. Vollstaendig abgeschlossen ist der Punkt erst, wenn auch Release/MinSizeRel, Doku und optional CI geprueft sind.
|
||||
|
||||
Erledigt:
|
||||
|
||||
- `CMakeLists.txt` target-basiert aufgeraeumt.
|
||||
- Globale `include_directories()` durch `target_include_directories()` ersetzt.
|
||||
- Doppelte Source-Einbindung entfernt.
|
||||
- Source-Erfassung auf Wildcards mit `CONFIGURE_DEPENDS` umgestellt.
|
||||
- `tray_windows.c` nicht mehr direkt im App-Target mitkompiliert.
|
||||
- `c_tray` Windows-Backend stabilisiert: fehlende C-Header ergaenzt, Win32-Wide-API explizit genutzt und den 64-bit `malloc`-Pointer-Crash beim Tray-Menue behoben.
|
||||
- Harte `/MTd`-CRT-Option aus `c_tray` entfernt, damit die Library zum restlichen MSVC-Debug-Build passt.
|
||||
- Ungenutztes `saucer-desktop` aus dem App-Link entfernt.
|
||||
- MSVC/Saucer-Fill-Konflikt fuer MSVC 19.44 behoben, indem Polyfill-Ersetzungen deaktiviert wurden.
|
||||
- `WIN32_LEAN_AND_MEAN` vom App-Target entfernt, weil es GDI+ Header unvollstaendig machte.
|
||||
- Reproduzierbaren lokalen Debug-Smoke-Build und App-Start geprueft.
|
||||
|
||||
Noch offen:
|
||||
|
||||
- Release-Build pruefen.
|
||||
- MinSizeRel-Build pruefen.
|
||||
- README um Build-Voraussetzungen und Build-Befehl ergaenzen.
|
||||
- Pruefen, ob generierte Build-/Embed-Artefakte sauber ignoriert werden.
|
||||
- Optional CI-Workflow fuer Debug/Release-Smoke-Build anlegen.
|
||||
|
||||
Reproduzierbarer lokaler Smoke-Build:
|
||||
|
||||
```powershell
|
||||
cmd /c "call ""C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"" >nul && cmake -S . -B cmake-build-debug && cmake --build cmake-build-debug --target Toolbox --config Debug"
|
||||
```
|
||||
|
||||
### 2. `ToolboxWindow` zerlegen
|
||||
|
||||
- Status: erledigt fuer die geplanten ersten Schnitte. `ToolRegistry`, `LuaRunner`, `ToolboxJsBridge`, `WindowPlacement` und `ResourceLoader` sind aus `ToolboxWindow` herausgezogen.
|
||||
- `ToolRegistry`: erledigt. Scannt `.Toolbox`, liest `description.txt`, `icon.png`, `start.lua`.
|
||||
- `LuaRunner`: erledigt. Startet/cancelt Lua-Tasks, verwaltet Timeout-Hook und haelt Task-State ausserhalb von `ToolboxWindow`.
|
||||
- `ToolboxJsBridge`: erledigt. Registriert `m_webview.expose(...)` ueber explizite Callback-Bindings.
|
||||
- `WindowPlacement`: erledigt. Buendelt DPI, Position, Groesse, Centering und Resize-Helfer.
|
||||
- `ResourceLoader`: erledigt. Buendelt embedded resources, Start-URL, `serve`, Icon, Titel und Scheme.
|
||||
|
||||
### 3. Threading und UI-Zugriff bereinigen
|
||||
|
||||
- Detached Threads vermeiden oder zentral verwalten.
|
||||
- Lua-Worker kontrolliert beenden.
|
||||
- UI-Updates nicht direkt aus Worker-Threads ausfuehren.
|
||||
- Sichtbarkeits- und Focus-State entweder UI-thread-lokal halten oder atomar absichern.
|
||||
|
||||
### 4. Frontend haerten
|
||||
|
||||
- `innerHTML`-Templates durch DOM-Erzeugung mit `textContent` ersetzen.
|
||||
- Daten aus `.Toolbox` nicht ungeescaped rendern.
|
||||
- Doppelte Render-Logik in `index.html` zusammenfuehren.
|
||||
- UI-Zustand fuer laufende Tools klarer modellieren.
|
||||
|
||||
### 5. Lua-Policy klaeren
|
||||
|
||||
- Entscheiden, ob Tools trusted code sind.
|
||||
- Falls nicht trusted: `os`, `io`, `package` standardmaessig deaktivieren oder Berechtigungen pro Tool einfuehren.
|
||||
- Fehler und Status gezielt an die UI melden, statt pauschal zu reloaden.
|
||||
|
||||
### 6. Win32-Helfer vereinheitlichen
|
||||
|
||||
- F12-Subclassing aus Toolbox- und Optionsfenster in einen gemeinsamen Helper ziehen.
|
||||
- `ShellExecuteW` statt `std::system` fuer Explorer/Terminal nutzen.
|
||||
- Encoding-Probleme in Quelltext und UI bereinigen.
|
||||
|
||||
### 7. Tests und Smoke Checks einfuehren
|
||||
|
||||
- Unit-Tests fuer `ToolConfigStore`.
|
||||
- Tests fuer Tool-Scanning ohne echte User-Home-Abhaengigkeit.
|
||||
- Debug/Release-Smoke-Build lokal und spaeter in CI.
|
||||
- Optional HTML-Lint oder DOM-Test fuer Renderfunktionen.
|
||||
28
inc/luaRunner.h
Normal file
28
inc/luaRunner.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
class LuaRunner {
|
||||
public:
|
||||
using CompletionCallback = std::function<void()>;
|
||||
|
||||
void run(int id, const std::filesystem::path& scriptPath, int timeoutSeconds, CompletionCallback onComplete);
|
||||
void cancel(int id);
|
||||
|
||||
private:
|
||||
struct LuaTask {
|
||||
std::shared_ptr<std::atomic_bool> cancel;
|
||||
};
|
||||
|
||||
struct State {
|
||||
std::mutex tasksMutex;
|
||||
std::unordered_map<int, LuaTask> tasks;
|
||||
};
|
||||
|
||||
std::shared_ptr<State> m_state = std::make_shared<State>();
|
||||
};
|
||||
11
inc/resourceLoader.h
Normal file
11
inc/resourceLoader.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <saucer/smartview.hpp>
|
||||
|
||||
class ResourceLoader {
|
||||
public:
|
||||
static bool loadToolboxShell(saucer::smartview& webview);
|
||||
static bool serveHtml(saucer::smartview& webview, const std::string& htmlPath, bool setDecorations);
|
||||
};
|
||||
29
inc/toolRegistry.h
Normal file
29
inc/toolRegistry.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ToolboxItem {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string icon;
|
||||
std::string lua_script_path_string;
|
||||
std::string description;
|
||||
std::string category;
|
||||
std::string version;
|
||||
std::string author;
|
||||
std::string url;
|
||||
std::string license;
|
||||
};
|
||||
|
||||
class ToolRegistry {
|
||||
public:
|
||||
static std::filesystem::path toolboxDirectory(const std::filesystem::path& userPath);
|
||||
static bool ensureToolboxDirectory(const std::filesystem::path& userPath);
|
||||
static std::vector<ToolboxItem> loadTools(const std::filesystem::path& userPath);
|
||||
|
||||
private:
|
||||
static void encodeIcon(const std::filesystem::path& imagePath, ToolboxItem& item);
|
||||
};
|
||||
28
inc/toolboxJsBridge.h
Normal file
28
inc/toolboxJsBridge.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <saucer/smartview.hpp>
|
||||
|
||||
class ToolboxJsBridge {
|
||||
public:
|
||||
struct Bindings {
|
||||
std::function<std::string()> getToolboxItems;
|
||||
std::function<void(int)> runLua;
|
||||
std::function<void(int)> cancelLua;
|
||||
std::function<void()> debugPrint;
|
||||
std::function<void()> openFileSystem;
|
||||
std::function<void()> openCommandPrompt;
|
||||
std::function<void(int)> openSettings;
|
||||
std::function<void()> openIndex;
|
||||
std::function<void()> getNewContent;
|
||||
std::function<int()> getLuaTimeoutSeconds;
|
||||
std::function<void(int)> setLuaTimeoutSeconds;
|
||||
std::function<std::string()> getActiveToolName;
|
||||
std::function<std::string()> getToolConfigIni;
|
||||
std::function<void(const std::string&)> setToolConfigIni;
|
||||
};
|
||||
|
||||
static void registerBindings(saucer::smartview& webview, Bindings bindings);
|
||||
};
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
#include <saucer/smartview.hpp>
|
||||
#include "modules/hwnd_module.h"
|
||||
#include <saucer/embedded/all.hpp>
|
||||
#include <sol/sol.hpp>
|
||||
#include <saucer/webview.hpp>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include "luaRunner.h"
|
||||
#include "optionWindow.h"
|
||||
#include "resourceLoader.h"
|
||||
#include "toolConfig.h"
|
||||
#include "toolboxJsBridge.h"
|
||||
#include "toolRegistry.h"
|
||||
#include "windowPlacement.h"
|
||||
|
||||
class ToolboxWindow {
|
||||
public:
|
||||
@@ -30,20 +31,6 @@ public:
|
||||
|
||||
std::atomic_bool isIndex = true;
|
||||
|
||||
struct ToolboxItem{
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string icon;
|
||||
std::string lua_script_path_string;
|
||||
std::string description;
|
||||
std::string category;
|
||||
std::string version;
|
||||
std::string author;
|
||||
std::string url;
|
||||
std::string license;
|
||||
};
|
||||
|
||||
std::vector<ToolboxItem> m_toolboxItems;
|
||||
|
||||
saucer::smartview m_webview;
|
||||
@@ -60,8 +47,6 @@ private:
|
||||
|
||||
std::string jsonString;
|
||||
|
||||
int size_x,size_y;
|
||||
|
||||
#ifdef _WIN32
|
||||
std::filesystem::path userPath = std::getenv("USERPROFILE"); // Windows
|
||||
#else
|
||||
@@ -75,7 +60,6 @@ private:
|
||||
void configureWindow();
|
||||
void moveWindowTo(int x, int y);
|
||||
void loadResources();
|
||||
void runLuaScript();
|
||||
|
||||
void ExposeToJs();
|
||||
|
||||
@@ -92,6 +76,8 @@ private:
|
||||
|
||||
void open_file_system();
|
||||
|
||||
void open_cmd();
|
||||
|
||||
void SetJsonItems();
|
||||
|
||||
void openEinst();
|
||||
@@ -107,14 +93,8 @@ private:
|
||||
void setLuaTimeoutSeconds(int seconds);
|
||||
std::string getActiveToolName();
|
||||
|
||||
struct LuaTask {
|
||||
std::shared_ptr<std::atomic_bool> cancel;
|
||||
};
|
||||
|
||||
std::mutex m_luaTasksMutex;
|
||||
std::unordered_map<int, LuaTask> m_luaTasks;
|
||||
|
||||
int m_activeToolId = -1;
|
||||
LuaRunner m_luaRunner;
|
||||
ToolConfigStore m_configStore;
|
||||
|
||||
std::filesystem::path getToolConfigPath(int id) const;
|
||||
|
||||
21
inc/trayController.h
Normal file
21
inc/trayController.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class ToolboxWindow;
|
||||
|
||||
class TrayController {
|
||||
public:
|
||||
explicit TrayController(ToolboxWindow& window);
|
||||
~TrayController();
|
||||
|
||||
TrayController(const TrayController&) = delete;
|
||||
TrayController& operator=(const TrayController&) = delete;
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
14
inc/windowPlacement.h
Normal file
14
inc/windowPlacement.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <saucer/smartview.hpp>
|
||||
|
||||
class WindowPlacement {
|
||||
public:
|
||||
static void configureToolbox(saucer::smartview& webview, HWND hwnd);
|
||||
static void moveTo(HWND hwnd, int x, int y);
|
||||
static void center(HWND hwnd);
|
||||
static void resize(saucer::smartview& webview, int width, int height);
|
||||
static void resizePercent(saucer::smartview& webview, double widthPercent, double heightPercent);
|
||||
};
|
||||
Submodule lib/c_tray updated: fa1e862259...fc77dda643
18
lib/toolbox_win/CMakeLists.txt
Normal file
18
lib/toolbox_win/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.30)
|
||||
|
||||
project(toolbox_win LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
add_library(toolbox_win STATIC
|
||||
src/icon_utils.cpp
|
||||
)
|
||||
|
||||
target_include_directories(toolbox_win
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(toolbox_win PUBLIC user32)
|
||||
endif ()
|
||||
12
lib/toolbox_win/include/toolbox_win/icon_utils.h
Normal file
12
lib/toolbox_win/include/toolbox_win/icon_utils.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
typedef void* HINSTANCE;
|
||||
typedef void* HICON;
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
HICON toolbox_load_small_icon(HINSTANCE instance, int resource_id);
|
||||
}
|
||||
26
lib/toolbox_win/src/icon_utils.cpp
Normal file
26
lib/toolbox_win/src/icon_utils.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "toolbox_win/icon_utils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
HICON toolbox_load_small_icon(HINSTANCE instance, int resource_id) {
|
||||
#ifdef _WIN32
|
||||
if (!instance) {
|
||||
instance = GetModuleHandle(NULL);
|
||||
}
|
||||
|
||||
return static_cast<HICON>(LoadImage(
|
||||
instance,
|
||||
MAKEINTRESOURCE(resource_id),
|
||||
IMAGE_ICON,
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
GetSystemMetrics(SM_CYSMICON),
|
||||
LR_SHARED
|
||||
));
|
||||
#else
|
||||
(void)instance;
|
||||
(void)resource_id;
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
79
main.cpp
79
main.cpp
@@ -1,82 +1,31 @@
|
||||
#include <saucer/app.hpp>
|
||||
#include <Windows.h>
|
||||
#include <saucer/app.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "toolboxWindow.h"
|
||||
#include "resource.h"
|
||||
#include "tray.h"
|
||||
#include "trayController.h"
|
||||
|
||||
import toolboxTray;
|
||||
coco::stray start(saucer::application* app) {
|
||||
std::shared_ptr<saucer::application> appShared(app, [](saucer::application*) {});
|
||||
|
||||
ToolboxWindow* g_toolboxWindow = nullptr;
|
||||
auto window = std::make_unique<ToolboxWindow>(appShared);
|
||||
window->initialize();
|
||||
|
||||
void on_quit(struct tray_menu *item) {
|
||||
tray_exit();
|
||||
}
|
||||
|
||||
void on_open(struct tray_menu *item = nullptr) {
|
||||
if (g_toolboxWindow) {
|
||||
if (g_toolboxWindow->m_bShow) {
|
||||
g_toolboxWindow->hide();
|
||||
} else {
|
||||
g_toolboxWindow->show();
|
||||
}
|
||||
TrayController tray(*window);
|
||||
if (!tray.initialize()) {
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
|
||||
coco::stray start(saucer::application *app){
|
||||
// Toolbox-Fenster erstellen und initialisieren
|
||||
std::shared_ptr<saucer::application> app_shared(app, [](saucer::application*){});
|
||||
auto toolboxWindow = std::make_unique<ToolboxWindow>(app_shared);
|
||||
toolboxWindow->initialize();
|
||||
|
||||
g_toolboxWindow = toolboxWindow.get();
|
||||
|
||||
HICON hIcon = static_cast<HICON>(LoadImage(
|
||||
GetModuleHandle(NULL),
|
||||
MAKEINTRESOURCE(IDI_ICON1),
|
||||
IMAGE_ICON,
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
GetSystemMetrics(SM_CYSMICON),
|
||||
LR_SHARED
|
||||
));
|
||||
|
||||
// Tray-Menue definieren
|
||||
tray_menu tray_menu_items[] = {
|
||||
{"Oeffnen", 0, 0, 0, on_open, nullptr},
|
||||
{"-", 0, 0, 0, nullptr, nullptr}, // Separator
|
||||
{"Beenden", 0, 0, 0, on_quit, nullptr},
|
||||
{nullptr, 0, 0, 0, nullptr, nullptr} // Ende des Menues
|
||||
};
|
||||
|
||||
// ToolboxTray erstellen
|
||||
toolboxTray tray("toolbox.ico", "Toolbox");
|
||||
tray.icon_handle = hIcon;
|
||||
tray.icon_is_shared = (hIcon != nullptr) ? 1 : 0;
|
||||
|
||||
// Linksklick-Aktion registrieren
|
||||
tray.setLeftClickAction([&]() {
|
||||
on_open(nullptr); // Ruft die Funktion ohne Menueeintrag auf
|
||||
});
|
||||
|
||||
// Tray-Menue registrieren
|
||||
tray.setTrayMenu(tray_menu_items);
|
||||
|
||||
if (tray_init(&tray) != 0) {
|
||||
fprintf(stderr, "Fehler beim Initialisieren des Tray-Icons\n");
|
||||
co_return; // Fehlerfall: coroutine beenden
|
||||
}
|
||||
|
||||
//toolboxWindow->show();
|
||||
|
||||
co_await app->finish();
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
int main(int argc, char** argv) {
|
||||
int main(int, char**) {
|
||||
return saucer::application::create({.id = "toolbox"})->run(start);
|
||||
}
|
||||
#else
|
||||
#include <Windows.h>
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
|
||||
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
|
||||
return saucer::application::create({.id = "toolbox"})->run(start);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4,147 +4,10 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Toolbox</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: linear-gradient(to bottom, #1e1e2e, #28293d);
|
||||
color: white;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* Linke Sidebar */
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: #2b2c3b;
|
||||
padding: 15px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
color: #ff79c6;
|
||||
font-size: 16px;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar ul li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sidebar ul li a {
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
background: #32334a;
|
||||
color: #fff;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.sidebar ul li a:hover {
|
||||
background: #ff79c6;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Hauptbereich mit flexibler Anzeige */
|
||||
.main-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #1b1b2b;
|
||||
padding: 15px 20px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #ff79c6;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background: #2b2c3b;
|
||||
border: none;
|
||||
color: #ff79c6;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #32334a;
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1;
|
||||
background: #1e1e2e;
|
||||
padding: 15px;
|
||||
display: none; /* Standardmäßig ausgeblendet */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editor textarea {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
background: #2b2c3b;
|
||||
color: #fff;
|
||||
border: 1px solid #ff79c6;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.editor pre {
|
||||
width: 80%;
|
||||
margin-top: 10px;
|
||||
background: #2b2c3b;
|
||||
color: #fff;
|
||||
border: 1px solid #32334a;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.general {
|
||||
flex: 1;
|
||||
background: #1e1e2e;
|
||||
padding: 15px;
|
||||
display: flex; /* Standardmäßig sichtbar */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: #1b1b2b;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="saucer://embedded/html/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<body class="settings-page">
|
||||
<div id="shell">
|
||||
<!-- Sidebar für Optionen -->
|
||||
<div class="sidebar">
|
||||
<h2>Einstellungen</h2>
|
||||
@@ -173,19 +36,18 @@
|
||||
</div>
|
||||
|
||||
<div class="general" id="general">
|
||||
<div style="width: 80%; max-width: 520px;">
|
||||
<div class="settings-card">
|
||||
<h3 style="margin: 0 0 10px 0;">Tool</h3>
|
||||
<div id="tool-name" style="margin-bottom: 20px; color: #ff79c6;">-</div>
|
||||
<div id="tool-name" class="settings-tool-name">-</div>
|
||||
|
||||
<label for="lua-timeout">Lua Timeout (Sekunden)</label>
|
||||
<input id="lua-timeout" type="number" min="0" step="1" value="30"
|
||||
style="width: 100%; margin-top: 6px; padding: 8px; border-radius: 4px; border: 1px solid #32334a; background: #2b2c3b; color: #fff;">
|
||||
<div style="margin-top: 6px; font-size: 12px; color: #aaa;">
|
||||
<input id="lua-timeout" class="settings-input" type="number" min="0" step="1" value="30">
|
||||
<div class="settings-help">
|
||||
0 = kein Timeout
|
||||
</div>
|
||||
|
||||
<button id="save-settings" class="back-button" style="margin-top: 16px;">Speichern</button>
|
||||
<div id="save-status" style="margin-top: 8px; font-size: 12px; color: #aaa;"></div>
|
||||
<button id="save-settings" class="back-button settings-save">Speichern</button>
|
||||
<div id="save-status" class="settings-status"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,6 +57,7 @@
|
||||
<!--Schmidti.Digital © 2024-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function goBack() {
|
||||
saucer.exposed.openIndex();
|
||||
@@ -221,13 +84,50 @@
|
||||
const output = document.getElementById('highlightedOutput');
|
||||
showContent('general');
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function highlightLua(code) {
|
||||
let escaped = escapeHtml(code);
|
||||
const tokens = [];
|
||||
|
||||
function stash(pattern, className, input) {
|
||||
return input.replace(pattern, (match) => {
|
||||
const key = `@@TOKEN${tokens.length}@@`;
|
||||
tokens.push(`<span class="${className}">${match}</span>`);
|
||||
return key;
|
||||
});
|
||||
}
|
||||
|
||||
escaped = stash(/--\[\[[\s\S]*?\]\]|--[^\n]*/g, 'code-comment', escaped);
|
||||
escaped = stash(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\[\[[\s\S]*?\]\]/g, 'code-string', escaped);
|
||||
|
||||
escaped = escaped.replace(/\b(\d+(\.\d+)?)\b/g, '<span class="code-number">$1</span>');
|
||||
|
||||
const keywords = [
|
||||
'and','break','do','else','elseif','end','false','for','function',
|
||||
'if','in','local','nil','not','or','repeat','return','then','true',
|
||||
'until','while'
|
||||
];
|
||||
const keywordRegex = new RegExp(`\\b(${keywords.join('|')})\\b`, 'g');
|
||||
escaped = escaped.replace(keywordRegex, '<span class="code-keyword">$1</span>');
|
||||
|
||||
tokens.forEach((value, index) => {
|
||||
escaped = escaped.replace(`@@TOKEN${index}@@`, value);
|
||||
});
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Synchronisiere den Inhalt von textarea mit dem Codeblock
|
||||
editor.addEventListener('input', () => {
|
||||
output.innerHTML = ''; // Vorheriges Highlighting entfernen
|
||||
output.textContent = editor.value; // Aktuellen Text setzen
|
||||
if (window.hljs && typeof hljs.highlightAll === 'function') {
|
||||
hljs.highlightAll();
|
||||
}
|
||||
output.innerHTML = highlightLua(editor.value);
|
||||
});
|
||||
|
||||
const toolName = document.getElementById('tool-name');
|
||||
@@ -259,3 +159,7 @@
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
175
res/html/einstellungen_custom.html
Normal file
175
res/html/einstellungen_custom.html
Normal file
@@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Toolbox</title>
|
||||
<link rel="stylesheet" href="saucer://embedded/html/styles.css">
|
||||
</head>
|
||||
<body class="settings-page">
|
||||
<div id="shell" class="settings-shell">
|
||||
<div class="resize-handle resize-top" data-webview-resize="t"></div>
|
||||
<div class="resize-handle resize-right" data-webview-resize="r"></div>
|
||||
<div class="resize-handle resize-bottom" data-webview-resize="b"></div>
|
||||
<div class="resize-handle resize-left" data-webview-resize="l"></div>
|
||||
<div class="resize-handle resize-top-left" data-webview-resize="tl"></div>
|
||||
<div class="resize-handle resize-top-right" data-webview-resize="tr"></div>
|
||||
<div class="resize-handle resize-bottom-left" data-webview-resize="bl"></div>
|
||||
<div class="resize-handle resize-bottom-right" data-webview-resize="br"></div>
|
||||
<div class="titlebar" data-webview-drag>
|
||||
<div class="titlebar-left" data-webview-drag>
|
||||
<button class="titlebar-btn" onclick="goBack()" data-webview-ignore>←</button>
|
||||
<span class="titlebar-title" data-webview-drag>Einstellungen</span>
|
||||
</div>
|
||||
<div class="titlebar-actions" data-webview-drag>
|
||||
<button class="titlebar-btn" data-webview-minimize data-webview-ignore>–</button>
|
||||
<button class="titlebar-btn close" data-webview-close data-webview-ignore>×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-body">
|
||||
<!-- Sidebar für Optionen -->
|
||||
<div class="sidebar">
|
||||
<h2>Einstellungen</h2>
|
||||
<ul>
|
||||
<li><a href="#" onclick="showContent('general')">Einstellungen</a></li>
|
||||
<li><a href="#" onclick="showContent('editor')">Skript</a></li>
|
||||
<li><a href="#" onclick="showContent('none')">Option 3</a></li>
|
||||
<li><a href="#" onclick="showContent('none')">Option 4</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Hauptcontainer -->
|
||||
<div class="main-container">
|
||||
<!-- Zentraler Editor (Standardmäßig versteckt) -->
|
||||
<div class="editor" id="editor">
|
||||
<label for="code-editor"></label>
|
||||
<textarea id="code-editor" placeholder="Write Lua code here..."></textarea>
|
||||
<pre><code id="highlightedOutput" class="language-lua"></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="general" id="general">
|
||||
<div class="settings-card">
|
||||
<h3 style="margin: 0 0 10px 0;">Tool</h3>
|
||||
<div id="tool-name" class="settings-tool-name">-</div>
|
||||
|
||||
<label for="lua-timeout">Lua Timeout (Sekunden)</label>
|
||||
<input id="lua-timeout" class="settings-input" type="number" min="0" step="1" value="30">
|
||||
<div class="settings-help">
|
||||
0 = kein Timeout
|
||||
</div>
|
||||
|
||||
<button id="save-settings" class="back-button settings-save">Speichern</button>
|
||||
<div id="save-status" class="settings-status"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<!--Schmidti.Digital © 2024-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function goBack() {
|
||||
saucer.exposed.openIndex();
|
||||
}
|
||||
|
||||
function showContent(option) {
|
||||
const general = document.getElementById('general');
|
||||
const editor = document.getElementById('editor');
|
||||
|
||||
if (option === 'editor') {
|
||||
editor.style.display = 'flex';
|
||||
general.style.display = 'none';
|
||||
} else if (option === 'general') {
|
||||
general.style.display = 'flex';
|
||||
editor.style.display = 'none';
|
||||
} else {
|
||||
editor.style.display = 'none';
|
||||
general.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const editor = document.getElementById('code-editor');
|
||||
const output = document.getElementById('highlightedOutput');
|
||||
showContent('general');
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function highlightLua(code) {
|
||||
let escaped = escapeHtml(code);
|
||||
const tokens = [];
|
||||
|
||||
function stash(pattern, className, input) {
|
||||
return input.replace(pattern, (match) => {
|
||||
const key = `@@TOKEN${tokens.length}@@`;
|
||||
tokens.push(`<span class="${className}">${match}</span>`);
|
||||
return key;
|
||||
});
|
||||
}
|
||||
|
||||
escaped = stash(/--\[\[[\s\S]*?\]\]|--[^\n]*/g, 'code-comment', escaped);
|
||||
escaped = stash(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\[\[[\s\S]*?\]\]/g, 'code-string', escaped);
|
||||
|
||||
escaped = escaped.replace(/\b(\d+(\.\d+)?)\b/g, '<span class="code-number">$1</span>');
|
||||
|
||||
const keywords = [
|
||||
'and','break','do','else','elseif','end','false','for','function',
|
||||
'if','in','local','nil','not','or','repeat','return','then','true',
|
||||
'until','while'
|
||||
];
|
||||
const keywordRegex = new RegExp(`\\b(${keywords.join('|')})\\b`, 'g');
|
||||
escaped = escaped.replace(keywordRegex, '<span class="code-keyword">$1</span>');
|
||||
|
||||
tokens.forEach((value, index) => {
|
||||
escaped = escaped.replace(`@@TOKEN${index}@@`, value);
|
||||
});
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Synchronisiere den Inhalt von textarea mit dem Codeblock
|
||||
editor.addEventListener('input', () => {
|
||||
output.innerHTML = highlightLua(editor.value);
|
||||
});
|
||||
|
||||
const toolName = document.getElementById('tool-name');
|
||||
const timeoutInput = document.getElementById('lua-timeout');
|
||||
const saveBtn = document.getElementById('save-settings');
|
||||
const saveStatus = document.getElementById('save-status');
|
||||
|
||||
saucer.exposed.getActiveToolName().then(name => {
|
||||
toolName.textContent = name || '-';
|
||||
});
|
||||
|
||||
saucer.exposed.getLuaTimeoutSeconds().then(value => {
|
||||
timeoutInput.value = value;
|
||||
});
|
||||
|
||||
|
||||
saveBtn.addEventListener('click', () => {
|
||||
const seconds = parseInt(timeoutInput.value, 10);
|
||||
saucer.exposed.setLuaTimeoutSeconds(Number.isFinite(seconds) ? seconds : 0);
|
||||
saveStatus.textContent = 'Gespeichert';
|
||||
setTimeout(() => {
|
||||
saveStatus.textContent = '';
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,13 +10,23 @@
|
||||
<body>
|
||||
<div id="shell">
|
||||
<div class="header">
|
||||
<button onclick="getNewContent()" class="button" id="reload-button">↻</button>
|
||||
<button onclick="openEinstFirst()" class="button" id="option-button" style="display: none;">⚙️</button>
|
||||
<div class="header-cluster header-left">
|
||||
<button onclick="getNewContent()" class="button icon-button" id="reload-button">↻</button>
|
||||
</div>
|
||||
<h1>Toolbox</h1>
|
||||
<button onclick="openFileSystem()" id="file-system-button" class="button">📁</button>
|
||||
<div class="header-cluster header-right">
|
||||
<div class="button-group">
|
||||
<button onclick="openCmd()" class="button icon-button" id="cmd-button">💻</button>
|
||||
<button onclick="openFileSystem()" id="file-system-button" class="button icon-button">📁</button>
|
||||
</div>
|
||||
<button onclick="openEinstFirst()" class="button icon-button" id="option-button" style="display: none;">⚙</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main" id="toolbox">
|
||||
<!-- Tool Cards werden hier dynamisch geladen -->
|
||||
<div class="add-tool-card" onclick="openAddTool()">
|
||||
<span class="plus-icon">+</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<!--Schmidti.Digital © 2024-->
|
||||
@@ -64,10 +74,19 @@
|
||||
saucer.exposed.open_file_system();
|
||||
}
|
||||
|
||||
function openCmd() {
|
||||
saucer.exposed.open_cmd();
|
||||
}
|
||||
|
||||
function openAddTool() {
|
||||
openFileSystem();
|
||||
}
|
||||
|
||||
function reloadContent() {
|
||||
const toolbox = document.getElementById('toolbox');
|
||||
|
||||
// Toolbox leeren
|
||||
// Toolbox leeren, aber das add-tool-card Element behalten
|
||||
const addToolCard = toolbox.querySelector('.add-tool-card');
|
||||
toolbox.innerHTML = '';
|
||||
|
||||
// Toolbox-Daten neu abrufen
|
||||
@@ -97,6 +116,11 @@
|
||||
toolbox.innerHTML += toolCardHTML;
|
||||
});
|
||||
|
||||
// Das add-tool-card Element wieder hinzufügen
|
||||
if (addToolCard) {
|
||||
toolbox.appendChild(addToolCard);
|
||||
}
|
||||
|
||||
// Überprüfen und Wiederherstellen des Zustands der Buttons
|
||||
document.querySelectorAll('.tool-card').forEach(card => {
|
||||
const toolId = card.getAttribute('data-tool-id');
|
||||
@@ -153,6 +177,9 @@
|
||||
|
||||
const toolbox = document.getElementById('toolbox');
|
||||
|
||||
// Das add-tool-card Element entfernen und später wieder hinzufügen
|
||||
const addToolCard = toolbox.querySelector('.add-tool-card');
|
||||
|
||||
saucer.exposed.getToolboxItems().then(jsonString => {
|
||||
const m_toolboxItems = JSON.parse(jsonString);
|
||||
|
||||
@@ -179,6 +206,11 @@
|
||||
toolbox.innerHTML += toolCardHTML;
|
||||
});
|
||||
|
||||
// Das add-tool-card Element wieder hinzufügen
|
||||
if (addToolCard) {
|
||||
toolbox.appendChild(addToolCard);
|
||||
}
|
||||
|
||||
// Überprüfen und Wiederherstellen des Zustands beim Laden der Seite
|
||||
document.querySelectorAll('.tool-card').forEach(card => {
|
||||
const toolId = card.getAttribute('data-tool-id');
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
height: 100vh;
|
||||
}
|
||||
#shell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
@@ -38,7 +39,11 @@
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(180deg, rgba(20, 45, 70, 0.85), rgba(12, 28, 45, 0.7));
|
||||
padding: 16px 20px;
|
||||
padding: 12px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 22px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
border-bottom: 1px solid rgba(140, 210, 255, 0.2);
|
||||
@@ -57,8 +62,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
.button {
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
background: linear-gradient(180deg, rgba(120, 210, 255, 0.35), rgba(60, 150, 220, 0.55));
|
||||
border: 1px solid rgba(140, 210, 255, 0.45);
|
||||
color: var(--ink);
|
||||
@@ -71,27 +74,50 @@
|
||||
}
|
||||
.button:hover {
|
||||
background: linear-gradient(180deg, rgba(150, 225, 255, 0.45), rgba(80, 170, 235, 0.7));
|
||||
transform: translateY(-52%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 18px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
}
|
||||
.button:active {
|
||||
transform: translateY(-48%);
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 4px 10px var(--shadow), inset 0 2px 6px rgba(0,0,0,0.25);
|
||||
}
|
||||
#file-system-button {
|
||||
top: 50%;
|
||||
right: 18px;
|
||||
.header-cluster {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 120px;
|
||||
}
|
||||
#reload-button {
|
||||
top: 50%;
|
||||
left: 18px;
|
||||
.header-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
#option-button {
|
||||
top: 50%;
|
||||
right: 92px;
|
||||
.header-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.button-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 999px;
|
||||
background: rgba(12, 28, 45, 0.55);
|
||||
border: 1px solid rgba(140, 210, 255, 0.25);
|
||||
box-shadow: inset 0 1px 0 var(--shine), 0 8px 16px rgba(4, 10, 16, 0.35);
|
||||
}
|
||||
.icon-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 19px;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--ink-soft);
|
||||
@@ -187,6 +213,36 @@
|
||||
color: #3b2a00;
|
||||
cursor: progress;
|
||||
}
|
||||
.add-tool-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 2px dashed rgba(140, 210, 255, 0.4);
|
||||
padding: 8px 12px;
|
||||
border-radius: 14px;
|
||||
margin-bottom: 12px;
|
||||
height: calc((62px + 24px) / 2);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.add-tool-card:hover {
|
||||
border-color: rgba(140, 210, 255, 0.7);
|
||||
background: rgba(18, 40, 60, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(4, 10, 16, 0.3);
|
||||
}
|
||||
.add-tool-card .plus-icon {
|
||||
font-size: 24px;
|
||||
color: var(--aqua-2);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
font-weight: 300;
|
||||
}
|
||||
.add-tool-card:hover .plus-icon {
|
||||
color: var(--aqua-1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.footer {
|
||||
background: linear-gradient(180deg, rgba(18, 40, 60, 0.65), rgba(8, 18, 28, 0.6));
|
||||
padding: 10px 20px;
|
||||
@@ -195,3 +251,273 @@
|
||||
color: #9bbfd6;
|
||||
border-top: 1px solid rgba(140, 210, 255, 0.2);
|
||||
}
|
||||
.settings-page {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
.settings-page #shell {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.settings-page .settings-shell {
|
||||
height: 100vh;
|
||||
}
|
||||
.settings-page .settings-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.settings-page .titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: linear-gradient(180deg, rgba(20, 45, 70, 0.85), rgba(12, 28, 45, 0.75));
|
||||
border-bottom: 1px solid rgba(140, 210, 255, 0.2);
|
||||
box-shadow: 0 10px 20px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.settings-page .titlebar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.settings-page .titlebar-title {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.settings-page .titlebar-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.settings-page .titlebar-btn {
|
||||
border: 1px solid rgba(140, 210, 255, 0.35);
|
||||
background: rgba(10, 26, 40, 0.7);
|
||||
color: var(--ink);
|
||||
width: 30px;
|
||||
height: 26px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: inset 0 1px 0 var(--shine);
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
.settings-page .titlebar-btn:hover {
|
||||
background: rgba(30, 65, 95, 0.8);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.settings-page .titlebar-btn.close:hover {
|
||||
background: rgba(160, 45, 55, 0.85);
|
||||
}
|
||||
.settings-page .resize-handle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
.settings-page .resize-top,
|
||||
.settings-page .resize-bottom {
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
height: 8px;
|
||||
}
|
||||
.settings-page .resize-left,
|
||||
.settings-page .resize-right {
|
||||
top: 6px;
|
||||
bottom: 6px;
|
||||
width: 8px;
|
||||
}
|
||||
.settings-page .resize-top {
|
||||
top: 0;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.settings-page .resize-bottom {
|
||||
bottom: 0;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
.settings-page .resize-left {
|
||||
left: 0;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.settings-page .resize-right {
|
||||
right: 0;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.settings-page .resize-top-left,
|
||||
.settings-page .resize-top-right,
|
||||
.settings-page .resize-bottom-left,
|
||||
.settings-page .resize-bottom-right {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.settings-page .resize-top-left {
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
.settings-page .resize-top-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
.settings-page .resize-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
.settings-page .resize-bottom-right {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
.settings-page .sidebar {
|
||||
width: 220px;
|
||||
padding: 16px;
|
||||
background: rgba(12, 28, 45, 0.7);
|
||||
border-right: 1px solid rgba(140, 210, 255, 0.2);
|
||||
box-shadow: 8px 0 18px var(--shadow);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.settings-page .sidebar h2 {
|
||||
color: var(--ink-soft);
|
||||
font-size: 16px;
|
||||
margin: 0 0 12px 0;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
.settings-page .sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.settings-page .sidebar ul li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.settings-page .sidebar ul li a {
|
||||
text-decoration: none;
|
||||
padding: 10px 12px;
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
background: var(--glass);
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: inset 0 1px 0 var(--shine);
|
||||
transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.settings-page .sidebar ul li a:hover {
|
||||
background: var(--glass-strong);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 16px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
}
|
||||
.settings-page .main-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
.settings-page .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
}
|
||||
.settings-page .header h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.settings-page .back-button {
|
||||
border: 1px solid rgba(140, 210, 255, 0.45);
|
||||
background: linear-gradient(180deg, rgba(120, 210, 255, 0.35), rgba(60, 150, 220, 0.55));
|
||||
color: var(--ink);
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 6px 12px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
.settings-page .back-button:hover {
|
||||
background: linear-gradient(180deg, rgba(150, 225, 255, 0.45), rgba(80, 170, 235, 0.7));
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 18px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
}
|
||||
.settings-page .editor,
|
||||
.settings-page .general {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
min-height: 0;
|
||||
}
|
||||
.settings-page .settings-card {
|
||||
width: min(80%, 520px);
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 12px 22px var(--shadow), inset 0 1px 0 var(--shine);
|
||||
}
|
||||
.settings-page .settings-tool-name {
|
||||
margin-bottom: 20px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.settings-page .settings-input,
|
||||
.settings-page .editor textarea {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--glass-border);
|
||||
background: rgba(12, 28, 45, 0.6);
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
box-shadow: inset 0 1px 0 var(--shine);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.settings-page .editor textarea {
|
||||
height: 70%;
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
}
|
||||
.settings-page .editor pre {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
background: rgba(12, 28, 45, 0.6);
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
box-shadow: inset 0 1px 0 var(--shine);
|
||||
}
|
||||
.settings-page .code-keyword {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.settings-page .code-string {
|
||||
color: #ffd27a;
|
||||
}
|
||||
.settings-page .code-number {
|
||||
color: #8fe7ff;
|
||||
}
|
||||
.settings-page .code-comment {
|
||||
color: #7aa0b5;
|
||||
font-style: italic;
|
||||
}
|
||||
.settings-page .settings-help,
|
||||
.settings-page .settings-status {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #9bbfd6;
|
||||
}
|
||||
.settings-page .settings-save {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
104
src/luaRunner.cpp
Normal file
104
src/luaRunner.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "luaRunner.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int hookInstructionCount = 10000;
|
||||
|
||||
struct LuaHookContext {
|
||||
std::shared_ptr<std::atomic_bool> cancel;
|
||||
std::chrono::steady_clock::time_point deadline;
|
||||
};
|
||||
|
||||
void luaCancelHook(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");
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point deadlineFromTimeout(int timeoutSeconds) {
|
||||
if (timeoutSeconds <= 0) {
|
||||
return (std::chrono::steady_clock::time_point::max)();
|
||||
}
|
||||
return std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSeconds);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void LuaRunner::run(int id, const std::filesystem::path& scriptPath, int timeoutSeconds, CompletionCallback onComplete) {
|
||||
auto cancelFlag = std::make_shared<std::atomic_bool>(false);
|
||||
auto state = m_state;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->tasksMutex);
|
||||
state->tasks[id] = LuaTask{cancelFlag};
|
||||
}
|
||||
|
||||
std::thread([state, id, scriptPath, timeoutSeconds, cancelFlag, onComplete = std::move(onComplete)]() mutable {
|
||||
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);
|
||||
|
||||
LuaHookContext ctx{
|
||||
cancelFlag,
|
||||
deadlineFromTimeout(timeoutSeconds)
|
||||
};
|
||||
|
||||
lua_State* L = lua.lua_state();
|
||||
*reinterpret_cast<LuaHookContext**>(lua_getextraspace(L)) = &ctx;
|
||||
lua_sethook(L, luaCancelHook, LUA_MASKCOUNT, hookInstructionCount);
|
||||
|
||||
try {
|
||||
std::cout << "Running Lua script: " << scriptPath.string() << std::endl;
|
||||
lua.script_file(scriptPath.string());
|
||||
} catch (const sol::error& e) {
|
||||
std::cerr << "Lua error: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->tasksMutex);
|
||||
state->tasks.erase(id);
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
}).detach();
|
||||
|
||||
std::cout << "Lua-Skript wurde gestartet." << std::endl;
|
||||
}
|
||||
|
||||
void LuaRunner::cancel(int id) {
|
||||
auto state = m_state;
|
||||
std::lock_guard<std::mutex> lock(state->tasksMutex);
|
||||
auto it = state->tasks.find(id);
|
||||
if (it != state->tasks.end() && it->second.cancel) {
|
||||
it->second.cancel->store(true, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
@@ -114,9 +114,10 @@ void optionWindow::configureWindow() {
|
||||
int windowHeight = static_cast<int>(screenHeight * (700.0 / 1080.0));
|
||||
|
||||
m_webview.parent().set_size({windowWidth, windowHeight});
|
||||
const saucer::color bg{0x1e, 0x1e, 0x2e, 0xFF};
|
||||
const saucer::color bg{0x0b, 0x1c, 0x2c, 0x00};
|
||||
m_webview.set_background(bg);
|
||||
m_webview.parent().set_background(bg);
|
||||
m_webview.parent().set_decorations(saucer::window::decoration::none);
|
||||
|
||||
int xPos = (screenWidth - windowWidth) / 2;
|
||||
int yPos = (screenHeight - windowHeight) / 2;
|
||||
@@ -135,14 +136,14 @@ void optionWindow::loadResources() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = resources.find("/html/einstellungen.html");
|
||||
auto it = resources.find("/html/einstellungen_custom.html");
|
||||
if (it == resources.end()) {
|
||||
std::cerr << "Fehler: 'einstellungen.html' wurde nicht gefunden!" << std::endl;
|
||||
std::cerr << "Fehler: 'einstellungen_custom.html' wurde nicht gefunden!" << std::endl;
|
||||
return;
|
||||
}
|
||||
m_webview.embed(resources);
|
||||
// Explicit URL to avoid accidental fallback to main index
|
||||
m_webview.set_url("saucer://embedded/html/einstellungen.html");
|
||||
m_webview.set_url("saucer://embedded/html/einstellungen_custom.html");
|
||||
m_webview.set_context_menu(false);
|
||||
|
||||
if (!resources.contains("/ico/toolbox.png")) {
|
||||
|
||||
69
src/resourceLoader.cpp
Normal file
69
src/resourceLoader.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "resourceLoader.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <saucer/embedded/all.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
std::string normalizeEmbeddedPath(const std::string& path) {
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
return "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool applyToolboxIcon(saucer::smartview& webview, const decltype(saucer::embedded::all())& resources) {
|
||||
const auto iconIt = resources.find("/ico/toolbox.png");
|
||||
if (iconIt == resources.end()) {
|
||||
std::cerr << "Fehler: 'toolbox.png' wurde nicht gefunden!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto windowIcon = saucer::icon::from(iconIt->second.content); windowIcon.has_value()) {
|
||||
webview.parent().set_icon(windowIcon.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cerr << "Fehler: 'toolbox.png' konnte nicht als Fenster-Icon geladen werden!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ResourceLoader::loadToolboxShell(saucer::smartview& webview) {
|
||||
auto resources = saucer::embedded::all();
|
||||
|
||||
if (!resources.contains("/html/card.html")) {
|
||||
std::cerr << "Fehler: 'card.html' wurde nicht gefunden!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!resources.contains("/html/index.html")) {
|
||||
std::cerr << "Fehler: 'index.html' wurde nicht gefunden!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
webview.embed(resources);
|
||||
webview.set_url("saucer://embedded/html/index.html");
|
||||
webview.set_context_menu(false);
|
||||
applyToolboxIcon(webview, resources);
|
||||
webview.parent().set_title("Toolbox");
|
||||
saucer::webview::register_scheme("Toolbox");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceLoader::serveHtml(saucer::smartview& webview, const std::string& htmlPath, bool setDecorations) {
|
||||
auto resources = saucer::embedded::all();
|
||||
const std::string key = normalizeEmbeddedPath(htmlPath);
|
||||
|
||||
if (!resources.contains(key)) {
|
||||
std::cerr << "Fehler: '" << key << "' wurde nicht gefunden!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
webview.serve(key);
|
||||
webview.parent().set_decorations(setDecorations ? saucer::window::decoration::full
|
||||
: saucer::window::decoration::none);
|
||||
return true;
|
||||
}
|
||||
88
src/toolRegistry.cpp
Normal file
88
src/toolRegistry.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "toolRegistry.h"
|
||||
|
||||
#include <cppcodec/base64_rfc4648.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
std::filesystem::path ToolRegistry::toolboxDirectory(const std::filesystem::path& userPath) {
|
||||
return userPath / ".Toolbox";
|
||||
}
|
||||
|
||||
bool ToolRegistry::ensureToolboxDirectory(const std::filesystem::path& userPath) {
|
||||
const std::filesystem::path toolboxPath = toolboxDirectory(userPath);
|
||||
if (std::filesystem::exists(toolboxPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (std::filesystem::create_directory(toolboxPath)) {
|
||||
std::cout << ".Toolbox Verzeichnis wurde erstellt." << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
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::vector<ToolboxItem> ToolRegistry::loadTools(const std::filesystem::path& userPath) {
|
||||
std::vector<ToolboxItem> items;
|
||||
const std::filesystem::path toolboxDir = toolboxDirectory(userPath);
|
||||
|
||||
if (!std::filesystem::exists(toolboxDir) || !std::filesystem::is_directory(toolboxDir)) {
|
||||
std::cerr << "Fehler: Das Verzeichnis .Toolbox existiert nicht." << std::endl;
|
||||
return items;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
encodeIcon(entry.path() / "icon.png", item);
|
||||
|
||||
const 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>());
|
||||
}
|
||||
|
||||
const std::filesystem::path luaScript = entry.path() / "start.lua";
|
||||
if (std::filesystem::exists(luaScript)) {
|
||||
item.lua_script_path_string = luaScript.string();
|
||||
}
|
||||
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
void ToolRegistry::encodeIcon(const std::filesystem::path& imagePath, ToolboxItem& item) {
|
||||
std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cerr << "Fehler beim Oeffnen 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;
|
||||
}
|
||||
|
||||
item.icon = "data:image/png;base64," + cppcodec::base64_rfc4648::encode(fileData);
|
||||
}
|
||||
59
src/toolboxJsBridge.cpp
Normal file
59
src/toolboxJsBridge.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "toolboxJsBridge.h"
|
||||
|
||||
void ToolboxJsBridge::registerBindings(saucer::smartview& webview, Bindings bindings) {
|
||||
webview.expose("getToolboxItems", [bindings]() -> std::string {
|
||||
return bindings.getToolboxItems();
|
||||
});
|
||||
|
||||
webview.expose("run_lua", [bindings](int id) {
|
||||
bindings.runLua(id);
|
||||
});
|
||||
|
||||
webview.expose("cancel_lua", [bindings](int id) {
|
||||
bindings.cancelLua(id);
|
||||
});
|
||||
|
||||
webview.expose("debug_print", [bindings]() {
|
||||
bindings.debugPrint();
|
||||
});
|
||||
|
||||
webview.expose("open_file_system", [bindings]() {
|
||||
bindings.openFileSystem();
|
||||
});
|
||||
|
||||
webview.expose("open_cmd", [bindings]() {
|
||||
bindings.openCommandPrompt();
|
||||
});
|
||||
|
||||
webview.expose("openEinst", [bindings](int id) {
|
||||
bindings.openSettings(id);
|
||||
});
|
||||
|
||||
webview.expose("openIndex", [bindings]() {
|
||||
bindings.openIndex();
|
||||
});
|
||||
|
||||
webview.expose("getNewContent", [bindings]() {
|
||||
bindings.getNewContent();
|
||||
});
|
||||
|
||||
webview.expose("getLuaTimeoutSeconds", [bindings]() -> int {
|
||||
return bindings.getLuaTimeoutSeconds();
|
||||
});
|
||||
|
||||
webview.expose("setLuaTimeoutSeconds", [bindings](int seconds) {
|
||||
bindings.setLuaTimeoutSeconds(seconds);
|
||||
});
|
||||
|
||||
webview.expose("getActiveToolName", [bindings]() -> std::string {
|
||||
return bindings.getActiveToolName();
|
||||
});
|
||||
|
||||
webview.expose("getToolConfigIni", [bindings]() -> std::string {
|
||||
return bindings.getToolConfigIni();
|
||||
});
|
||||
|
||||
webview.expose("setToolConfigIni", [bindings](const std::string& content) {
|
||||
bindings.setToolConfigIni(content);
|
||||
});
|
||||
}
|
||||
@@ -13,16 +13,10 @@
|
||||
#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>
|
||||
@@ -112,121 +106,25 @@ void ToolboxWindow::setupToolboxWindow() {
|
||||
}
|
||||
|
||||
void ToolboxWindow::startThreadMonitor() {
|
||||
m_threadMonitor = std::thread([&]() {
|
||||
m_threadMonitor = std::thread([this]() {
|
||||
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;
|
||||
return ResourceLoader::serveHtml(m_webview, htmlPath, setDecorations);
|
||||
}
|
||||
|
||||
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);
|
||||
WindowPlacement::configureToolbox(m_webview, m_hwnd);
|
||||
}
|
||||
|
||||
void ToolboxWindow::moveWindowTo(int x, int y) {
|
||||
if (const HWND hwnd = m_hwnd) {
|
||||
SetWindowPos(hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
||||
}
|
||||
WindowPlacement::moveTo(m_hwnd, x, y);
|
||||
}
|
||||
|
||||
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!')");
|
||||
ResourceLoader::loadToolboxShell(m_webview);
|
||||
}
|
||||
|
||||
void ToolboxWindow::show() {
|
||||
@@ -265,58 +163,21 @@ void ToolboxWindow::hide() {
|
||||
}
|
||||
|
||||
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);
|
||||
ToolboxJsBridge::registerBindings(m_webview, {
|
||||
.getToolboxItems = [this]() { return jsonString; },
|
||||
.runLua = [this](int id) { run_lua(id); },
|
||||
.cancelLua = [this](int id) { cancel_lua(id); },
|
||||
.debugPrint = [this]() { debug_print(); },
|
||||
.openFileSystem = [this]() { open_file_system(); },
|
||||
.openCommandPrompt = [this]() { open_cmd(); },
|
||||
.openSettings = [this](int id) { openEinst(id); },
|
||||
.openIndex = [this]() { openIndex(); },
|
||||
.getNewContent = [this]() { getNewContent(); },
|
||||
.getLuaTimeoutSeconds = [this]() { return getLuaTimeoutSeconds(); },
|
||||
.setLuaTimeoutSeconds = [this](int seconds) { setLuaTimeoutSeconds(seconds); },
|
||||
.getActiveToolName = [this]() { return getActiveToolName(); },
|
||||
.getToolConfigIni = [this]() { return getToolConfigIni(); },
|
||||
.setToolConfigIni = [this](const std::string& content) { setToolConfigIni(content); },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -344,213 +205,38 @@ void ToolboxWindow::SetJsonItems() {// Konvertierung des Vektors in JSON
|
||||
}
|
||||
|
||||
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;
|
||||
return ToolRegistry::ensureToolboxDirectory(userPath);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
m_toolboxItems = ToolRegistry::loadTools(userPath);
|
||||
}
|
||||
|
||||
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};
|
||||
if (id < 0 || id >= static_cast<int>(m_toolboxItems.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
const std::filesystem::path scriptPath = m_toolboxItems[id].lua_script_path_string;
|
||||
m_luaRunner.run(id, scriptPath, getLuaTimeoutSecondsForId(id), [this, id]() {
|
||||
m_webview.saucer::webview::execute(std::format("enableButtonByToolId({})", id));
|
||||
m_webview.reload();
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
m_luaRunner.cancel(id);
|
||||
}
|
||||
|
||||
void ToolboxWindow::debug_print() {
|
||||
std::cout << "ToolboxWindow::debug_print()" << std::endl;
|
||||
}
|
||||
|
||||
ToolboxWindow::~ToolboxWindow() {
|
||||
m_runningMonitor = false;
|
||||
if (m_threadMonitor.joinable()) {
|
||||
m_threadMonitor.join();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -598,6 +284,23 @@ void ToolboxWindow::open_file_system() {
|
||||
m_webview.saucer::webview::execute("reloadContent()");
|
||||
}
|
||||
|
||||
void ToolboxWindow::open_cmd() {
|
||||
std::string command;
|
||||
|
||||
#ifdef _WIN32
|
||||
command = "cmd.exe";
|
||||
ShellExecute(NULL, L"open", L"cmd.exe", NULL, NULL, SW_SHOWNORMAL);
|
||||
#elif __APPLE__
|
||||
command = "open -a Terminal";
|
||||
std::system(command.c_str());
|
||||
#elif __linux__
|
||||
command = "x-terminal-emulator";
|
||||
std::system(command.c_str());
|
||||
#else
|
||||
std::cerr << "Fehler: Plattform nicht unterstützt." << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Beispielhafte Nutzung:
|
||||
void ToolboxWindow::openEinst() {
|
||||
@@ -627,46 +330,17 @@ void ToolboxWindow::openEinst(int id) {
|
||||
}
|
||||
|
||||
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);
|
||||
WindowPlacement::center(m_hwnd);
|
||||
}
|
||||
|
||||
void ToolboxWindow::resizeWindow(int width, int height)
|
||||
{
|
||||
// Stellen Sie die Größe des Fensters ein
|
||||
m_webview.parent().set_size({width, height});
|
||||
WindowPlacement::resize(m_webview, 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});
|
||||
WindowPlacement::resizePercent(m_webview, widthPercent, heightPercent);
|
||||
}
|
||||
|
||||
void ToolboxWindow::openIndex() {
|
||||
|
||||
241
src/trayController.cpp
Normal file
241
src/trayController.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include "trayController.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
#include "resource.h"
|
||||
#include "toolboxWindow.h"
|
||||
#include "toolbox_win/icon_utils.h"
|
||||
#include "tray.h"
|
||||
|
||||
import toolboxTray;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr DWORD minExplorerRestartDelayMs = 500;
|
||||
constexpr int maxTrayReinitAttempts = 5;
|
||||
constexpr int maxRetryDelayMs = 5000;
|
||||
|
||||
struct TrayState {
|
||||
bool reinitScheduled = false;
|
||||
int reinitAttempts = 0;
|
||||
DWORD lastExplorerRestartTime = 0;
|
||||
};
|
||||
|
||||
void onQuit(tray_menu*) {
|
||||
tray_exit();
|
||||
}
|
||||
|
||||
void openCommandPrompt(tray_menu*) {
|
||||
ShellExecuteW(nullptr, L"open", L"cmd.exe", nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
bool isExplorerReady() {
|
||||
const HWND taskbar = FindWindowW(L"Shell_TrayWnd", nullptr);
|
||||
return taskbar && IsWindow(taskbar) && IsWindowVisible(taskbar) && GetDesktopWindow();
|
||||
}
|
||||
|
||||
bool isTaskbarWindow(HWND hwnd) {
|
||||
const HWND taskbar = FindWindowW(L"Shell_TrayWnd", nullptr);
|
||||
return taskbar && hwnd == taskbar;
|
||||
}
|
||||
|
||||
int retryDelayMs(int attempt) {
|
||||
const int delay = 200 * (1 << std::max(0, attempt - 1));
|
||||
return std::min(delay, maxRetryDelayMs);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct TrayController::Impl {
|
||||
explicit Impl(ToolboxWindow& toolboxWindow)
|
||||
: window(toolboxWindow),
|
||||
menuItems{{
|
||||
{"CMD", 0, 0, 0, openCommandPrompt, nullptr},
|
||||
{"-", 0, 0, 0, nullptr, nullptr},
|
||||
{"Oeffnen", 0, 0, 0, &Impl::toggleToolboxWindow, nullptr},
|
||||
{"-", 0, 0, 0, nullptr, nullptr},
|
||||
{"Beenden", 0, 0, 0, onQuit, nullptr},
|
||||
{nullptr, 0, 0, 0, nullptr, nullptr},
|
||||
}} {}
|
||||
|
||||
bool initialize() {
|
||||
activeController.store(this);
|
||||
|
||||
const HICON icon = toolbox_load_small_icon(GetModuleHandleW(nullptr), IDI_ICON1);
|
||||
trayIcon = std::make_unique<toolboxTray>("toolbox.ico", "Toolbox");
|
||||
trayIcon->icon_handle = icon;
|
||||
trayIcon->icon_is_shared = icon ? 1 : 0;
|
||||
trayIcon->setLeftClickAction([] { toggleToolboxWindow(nullptr); });
|
||||
trayIcon->setTrayMenu(menuItems.data());
|
||||
|
||||
if (tray_init(trayIcon.get()) != 0) {
|
||||
std::fprintf(stderr, "Fehler beim Initialisieren des Tray-Icons\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
installExplorerHook();
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
uninstallExplorerHook();
|
||||
|
||||
Impl* expected = this;
|
||||
activeController.compare_exchange_strong(expected, nullptr);
|
||||
|
||||
trayIcon.reset();
|
||||
}
|
||||
|
||||
static Impl* current() {
|
||||
return activeController.load();
|
||||
}
|
||||
|
||||
static void toggleToolboxWindow(tray_menu*) {
|
||||
Impl* controller = current();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
ToolboxWindow& window = controller->window;
|
||||
if (window.m_bShow) {
|
||||
window.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
window.show();
|
||||
}
|
||||
|
||||
bool reinitTray() {
|
||||
if (!trayIcon || state.reinitScheduled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++state.reinitAttempts;
|
||||
if (state.reinitAttempts > maxTrayReinitAttempts) {
|
||||
std::fprintf(stderr, "Maximale Anzahl an Reinitialisierungsversuchen erreicht\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
state.reinitScheduled = true;
|
||||
int result = tray_init(trayIcon.get());
|
||||
|
||||
if (result != 0) {
|
||||
std::fprintf(stderr, "Fehler beim Re-Initialisieren des Tray-Icons (Versuch %d/%d)\n",
|
||||
state.reinitAttempts, maxTrayReinitAttempts);
|
||||
|
||||
Sleep(retryDelayMs(state.reinitAttempts));
|
||||
result = tray_init(trayIcon.get());
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
std::printf("Tray erfolgreich neu initialisiert\n");
|
||||
state.reinitAttempts = 0;
|
||||
}
|
||||
|
||||
state.reinitScheduled = false;
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
void retryTrayReinitAsync() {
|
||||
std::thread([] {
|
||||
Sleep(500);
|
||||
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
Impl* controller = current();
|
||||
if (controller && isExplorerReady() && controller->reinitTray()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sleep(500 * (attempt + 1));
|
||||
}
|
||||
|
||||
std::fprintf(stderr, "Tray konnte nach Explorer-Neustart nicht wiederhergestellt werden\n");
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void scheduleTrayReinit() {
|
||||
const DWORD now = GetTickCount();
|
||||
if (now - state.lastExplorerRestartTime < minExplorerRestartDelayMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.lastExplorerRestartTime = now;
|
||||
Sleep(300);
|
||||
|
||||
if (!reinitTray()) {
|
||||
retryTrayReinitAsync();
|
||||
}
|
||||
}
|
||||
|
||||
static void CALLBACK handleExplorerEvent(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG, DWORD,
|
||||
DWORD) {
|
||||
Impl* controller = current();
|
||||
if (!controller || idObject != OBJID_WINDOW || !isTaskbarWindow(hwnd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == EVENT_OBJECT_CREATE) {
|
||||
std::printf("Explorer-Neustart erkannt (Taskbar neu erstellt)\n");
|
||||
std::printf(isExplorerReady()
|
||||
? "Explorer ist bereit - Tray wird neu initialisiert\n"
|
||||
: "Explorer noch nicht bereit - Warten...\n");
|
||||
controller->scheduleTrayReinit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == EVENT_OBJECT_DESTROY) {
|
||||
std::printf("Explorer wird heruntergefahren\n");
|
||||
}
|
||||
}
|
||||
|
||||
void installExplorerHook() {
|
||||
explorerHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, nullptr, handleExplorerEvent, 0, 0,
|
||||
WINEVENT_OUTOFCONTEXT);
|
||||
|
||||
if (!explorerHook) {
|
||||
std::fprintf(stderr, "Warnung: Explorer-Neustart-Ueberwachung konnte nicht initialisiert werden\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::printf("Explorer-Neustart-Ueberwachung erfolgreich initialisiert\n");
|
||||
}
|
||||
|
||||
void uninstallExplorerHook() {
|
||||
if (explorerHook) {
|
||||
UnhookWinEvent(explorerHook);
|
||||
explorerHook = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ToolboxWindow& window;
|
||||
std::unique_ptr<toolboxTray> trayIcon;
|
||||
std::array<tray_menu, 6> menuItems;
|
||||
TrayState state;
|
||||
HWINEVENTHOOK explorerHook = nullptr;
|
||||
static std::atomic<Impl*> activeController;
|
||||
};
|
||||
|
||||
std::atomic<TrayController::Impl*> TrayController::Impl::activeController = nullptr;
|
||||
|
||||
TrayController::TrayController(ToolboxWindow& window)
|
||||
: m_impl(std::make_unique<Impl>(window)) {}
|
||||
|
||||
TrayController::~TrayController() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool TrayController::initialize() {
|
||||
return m_impl->initialize();
|
||||
}
|
||||
|
||||
void TrayController::shutdown() {
|
||||
if (m_impl) {
|
||||
m_impl->shutdown();
|
||||
}
|
||||
}
|
||||
92
src/windowPlacement.cpp
Normal file
92
src/windowPlacement.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "windowPlacement.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int toolboxWidth = 420;
|
||||
constexpr int toolboxHeight = 720;
|
||||
constexpr int fallbackPosition = 10;
|
||||
|
||||
struct DpiScale {
|
||||
double x = 1.0;
|
||||
double y = 1.0;
|
||||
};
|
||||
|
||||
DpiScale currentDpiScale() {
|
||||
HDC hdc = GetDC(nullptr);
|
||||
const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
|
||||
ReleaseDC(nullptr, hdc);
|
||||
|
||||
return {
|
||||
static_cast<double>(dpiX) / 96.0,
|
||||
static_cast<double>(dpiY) / 96.0
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WindowPlacement::configureToolbox(saucer::smartview& webview, HWND hwnd) {
|
||||
SetProcessDPIAware();
|
||||
|
||||
const DpiScale scale = currentDpiScale();
|
||||
webview.parent().set_size({toolboxWidth, toolboxHeight});
|
||||
|
||||
const saucer::color bg{0x0b, 0x1c, 0x2c, 0x00};
|
||||
webview.set_background(bg);
|
||||
webview.parent().set_background(bg);
|
||||
|
||||
RECT workArea;
|
||||
SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
|
||||
|
||||
const int logicalWorkWidth = static_cast<int>((workArea.right - workArea.left) / scale.x);
|
||||
const int logicalWorkHeight = static_cast<int>((workArea.bottom - workArea.top) / scale.y);
|
||||
|
||||
int xPos = logicalWorkWidth - toolboxWidth;
|
||||
int yPos = logicalWorkHeight - toolboxHeight;
|
||||
|
||||
if (xPos < 0) {
|
||||
xPos = fallbackPosition;
|
||||
}
|
||||
if (yPos < 0) {
|
||||
yPos = fallbackPosition;
|
||||
}
|
||||
|
||||
moveTo(hwnd, static_cast<int>(xPos * scale.x), static_cast<int>(yPos * scale.y));
|
||||
}
|
||||
|
||||
void WindowPlacement::moveTo(HWND hwnd, int x, int y) {
|
||||
if (hwnd) {
|
||||
SetWindowPos(hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowPlacement::center(HWND hwnd) {
|
||||
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
|
||||
int windowWidth = 0;
|
||||
int windowHeight = 0;
|
||||
|
||||
if (hwnd) {
|
||||
RECT rect;
|
||||
if (GetWindowRect(hwnd, &rect)) {
|
||||
windowWidth = rect.right - rect.left;
|
||||
windowHeight = rect.bottom - rect.top;
|
||||
}
|
||||
}
|
||||
|
||||
moveTo(hwnd, (screenWidth - windowWidth) / 2, (screenHeight - windowHeight) / 2);
|
||||
}
|
||||
|
||||
void WindowPlacement::resize(saucer::smartview& webview, int width, int height) {
|
||||
webview.parent().set_size({width, height});
|
||||
}
|
||||
|
||||
void WindowPlacement::resizePercent(saucer::smartview& webview, double widthPercent, double heightPercent) {
|
||||
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
||||
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
||||
|
||||
resize(webview,
|
||||
static_cast<int>(screenWidth * (widthPercent / 100.0)),
|
||||
static_cast<int>(screenHeight * (heightPercent / 100.0)));
|
||||
}
|
||||
Reference in New Issue
Block a user